aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--TODO-RELEASE3
-rw-r--r--configure.ac20
-rwxr-xr-xcontrib/jenkins_arm.sh1
-rw-r--r--debian/control1
-rw-r--r--include/osmocom/codec/ecu.h63
-rw-r--r--include/osmocom/core/fsm.h1
-rw-r--r--include/osmocom/core/logging.h17
-rw-r--r--include/osmocom/core/logging_internal.h3
-rw-r--r--include/osmocom/core/msgb.h4
-rw-r--r--include/osmocom/core/select.h2
-rw-r--r--include/osmocom/core/sockaddr_str.h9
-rw-r--r--include/osmocom/core/socket.h7
-rw-r--r--include/osmocom/core/talloc.h28
-rw-r--r--include/osmocom/core/tdef.h7
-rw-r--r--include/osmocom/gprs/gprs_bssgp.h2
-rw-r--r--include/osmocom/gsm/bts_features.h1
-rw-r--r--include/osmocom/gsm/gsm0502.h12
-rw-r--r--include/osmocom/gsm/gsm0808_utils.h1
-rw-r--r--include/osmocom/gsm/gsm_utils.h4
-rw-r--r--include/osmocom/gsm/gsup.h7
-rw-r--r--include/osmocom/gsm/l1sap.h10
-rw-r--r--include/osmocom/gsm/mncc.h8
-rw-r--r--include/osmocom/gsm/protocol/gsm_04_08.h43
-rw-r--r--include/osmocom/gsm/protocol/gsm_08_08.h4
-rw-r--r--include/osmocom/gsm/protocol/gsm_08_58.h2
-rw-r--r--include/osmocom/vty/tdef_vty.h3
-rw-r--r--libosmocore.pc.in4
-rw-r--r--m4/ax_pthread.m4486
-rwxr-xr-xosmo-release.sh12
-rw-r--r--src/Makefile.am6
-rw-r--r--src/codec/Makefile.am2
-rw-r--r--src/codec/ecu.c118
-rw-r--r--src/codec/ecu_fr.c51
-rw-r--r--src/context.c52
-rw-r--r--src/fsm.c71
-rw-r--r--src/gb/gprs_bssgp.c9
-rw-r--r--src/gb/gprs_bssgp_vty.c10
-rw-r--r--src/gb/gprs_ns_vty.c14
-rw-r--r--src/gb/libosmogb.map1
-rw-r--r--src/gsm/bts_features.c1
-rw-r--r--src/gsm/cbsp.c13
-rw-r--r--src/gsm/gsm0502.c157
-rw-r--r--src/gsm/gsm0808_utils.c21
-rw-r--r--src/gsm/gsm48.c21
-rw-r--r--src/gsm/gsm_utils.c4
-rw-r--r--src/gsm/gsup.c35
-rw-r--r--src/gsm/libosmogsm.map2
-rw-r--r--src/gsm/rsl.c1
-rw-r--r--src/logging.c68
-rw-r--r--src/select.c60
-rw-r--r--src/serial.c2
-rw-r--r--src/sim/class_tables.c2
-rw-r--r--src/sockaddr_str.c29
-rw-r--r--src/socket.c319
-rw-r--r--src/tdef.c74
-rw-r--r--src/vty/command.c20
-rw-r--r--src/vty/logging_vty.c207
-rw-r--r--src/vty/tdef_vty.c35
-rw-r--r--src/vty/vty.c19
-rw-r--r--tests/Makefile.am18
-rw-r--r--tests/codec/codec_ecu_fr_test.c88
-rw-r--r--tests/codec/codec_ecu_fr_test.ok219
-rw-r--r--tests/context/context_test.c84
-rw-r--r--tests/context/context_test.ok4
-rw-r--r--tests/fsm/fsm_dealloc_test.c69
-rw-r--r--tests/fsm/fsm_dealloc_test.err3694
-rw-r--r--tests/gsm0408/gsm0408_test.c44
-rw-r--r--tests/gsm0502/gsm0502_test.c159
-rw-r--r--tests/gsm0502/gsm0502_test.ok271
-rw-r--r--tests/gsm29205/gsm29205_test.c8
-rw-r--r--tests/gsup/gsup_test.err14
-rw-r--r--tests/logging/logging_vty_test.c1
-rw-r--r--tests/sockaddr_str/sockaddr_str_test.c5
-rw-r--r--tests/sockaddr_str/sockaddr_str_test.ok76
-rw-r--r--tests/tdef/tdef_test.c28
-rw-r--r--tests/tdef/tdef_test.ok13
-rw-r--r--tests/tdef/tdef_vty_test_config_root.c3
-rw-r--r--tests/tdef/tdef_vty_test_config_root.vty50
-rw-r--r--tests/testsuite.at12
-rw-r--r--tests/vty/fail_cmd_ret_warning.cfg3
-rw-r--r--tests/vty/ok_deprecated_logging.cfg3
-rw-r--r--tests/vty/vty_test.c24
-rw-r--r--tests/vty/vty_test.ok6
-rw-r--r--utils/Makefile.am4
84 files changed, 5851 insertions, 1238 deletions
diff --git a/TODO-RELEASE b/TODO-RELEASE
index 665ecf79..692bdc18 100644
--- a/TODO-RELEASE
+++ b/TODO-RELEASE
@@ -8,3 +8,6 @@
# If any interfaces have been removed or changed since the last public release: c:r:0.
#library what description / commit summary line
core osmo_tdef_get() change val_if_not_present arg from unsigned long to long to allow passing -1
+core struct osmo_tdef fields min_val,max_val added, ABI break (arrays of structs used in programs)
+gsm API added osmo_gsm48_rfpowercap2powerclass()
+gb API added bssgp_bvc_ctx_free()
diff --git a/configure.ac b/configure.ac
index 7ad5908d..6c54e666 100644
--- a/configure.ac
+++ b/configure.ac
@@ -68,6 +68,9 @@ AC_SUBST(LIBRARY_DLSYM)
AC_CHECK_LIB(execinfo, backtrace, BACKTRACE_LIB=-lexecinfo, BACKTRACE_LIB=)
AC_SUBST(BACKTRACE_LIB)
+# check for pthread (PTHREAD_CFLAGS, PTHREAD_LIBS)
+AX_PTHREAD
+
# check for old glibc < 2.17 to get clock_gettime
AC_SEARCH_LIBS([clock_gettime], [rt posix4], [LIBRARY_RT="$LIBS";LIBS=""])
AC_SUBST(LIBRARY_RT)
@@ -157,6 +160,22 @@ then
AC_DEFINE([USE_GNUTLS], [1], [Use GnuTLS as a fallback for missing getrandom()])
fi
+AC_ARG_ENABLE([libsctp], [AS_HELP_STRING([--disable-libsctp], [Do not enable socket multiaddr APIs requiring libsctp])],
+ [ENABLE_LIBSCTP=$enableval], [ENABLE_LIBSCTP="yes"])
+AM_CONDITIONAL(ENABLE_LIBSCTP, test x"$ENABLE_LIBSCTP" = x"yes")
+AS_IF([test "x$ENABLE_LIBSCTP" = "xyes"], [
+ old_LIBS=$LIBS
+ AC_SEARCH_LIBS([sctp_bindx], [sctp], [
+ AC_DEFINE(HAVE_LIBSCTP, 1, [Define 1 to enable SCTP support])
+ AC_SUBST(HAVE_LIBSCTP, [1])
+ if test -n "$ac_lib"; then
+ AC_SUBST(LIBSCTP_LIBS, [-l$ac_lib])
+ fi
+ ], [
+ AC_MSG_ERROR([sctp_bindx not found in searched libs])])
+ LIBS=$old_LIBS
+])
+
AC_ARG_ENABLE(plugin,
[AS_HELP_STRING(
[--disable-plugin],
@@ -265,6 +284,7 @@ then
AM_CONDITIONAL(ENABLE_UTILITIES, false)
AM_CONDITIONAL(ENABLE_GB, false)
AM_CONDITIONAL(ENABLE_GNUTLS, false)
+ AM_CONDITIONAL(ENABLE_LIBSCTP, false)
AM_CONDITIONAL(ENABLE_PCSC, false)
AM_CONDITIONAL(ENABLE_PSEUDOTALLOC, true)
AM_CONDITIONAL(ENABLE_SERCOM_STUB, true)
diff --git a/contrib/jenkins_arm.sh b/contrib/jenkins_arm.sh
index e3a6cd14..8ee90f03 100755
--- a/contrib/jenkins_arm.sh
+++ b/contrib/jenkins_arm.sh
@@ -18,6 +18,7 @@ build() {
--enable-embedded \
--disable-doxygen \
--disable-shared \
+ --disable-libsctp \
--enable-external-tests \
CFLAGS="-Os -ffunction-sections -fdata-sections -nostartfiles -nodefaultlibs $WERROR_FLAGS"
diff --git a/debian/control b/debian/control
index 07163da2..6c9cfae9 100644
--- a/debian/control
+++ b/debian/control
@@ -15,6 +15,7 @@ Build-Depends: debhelper (>= 9),
libpcsclite-dev,
pkg-config,
libtalloc-dev,
+ libsctp-dev,
python (>= 2.7.6)
Standards-Version: 3.9.8
Vcs-Git: git://git.osmocom.org/libosmocore.git
diff --git a/include/osmocom/codec/ecu.h b/include/osmocom/codec/ecu.h
index ec0a2f8d..99b1430f 100644
--- a/include/osmocom/codec/ecu.h
+++ b/include/osmocom/codec/ecu.h
@@ -5,11 +5,68 @@
#include <osmocom/codec/codec.h>
-/* Codec independent ECU state */
+/* ECU state for GSM-FR */
struct osmo_ecu_fr_state {
bool subsequent_lost_frame;
uint8_t frame_backup[GSM_FR_BYTES];
};
-void osmo_ecu_fr_reset(struct osmo_ecu_fr_state *state, const uint8_t *frame);
-int osmo_ecu_fr_conceal(struct osmo_ecu_fr_state *state, uint8_t *frame);
+void osmo_ecu_fr_reset(struct osmo_ecu_fr_state *state, const uint8_t *frame)
+ OSMO_DEPRECATED("Use generic ECU abstraction layer instead");
+int osmo_ecu_fr_conceal(struct osmo_ecu_fr_state *state, uint8_t *frame)
+ OSMO_DEPRECATED("Use generic ECU abstraction layer instead");
+
+enum osmo_ecu_codec {
+ OSMO_ECU_CODEC_HR,
+ OSMO_ECU_CODEC_FR,
+ OSMO_ECU_CODEC_EFR,
+ OSMO_ECU_CODEC_AMR,
+ _NUM_OSMO_ECU_CODECS
+};
+
+/***********************************************************************
+ * Generic ECU abstraction layer below
+ ***********************************************************************/
+
+/* As the developer and copyright holder of the related code, I hereby
+ * state that any ECU implementation using 'struct osmo_ecu_ops' and
+ * registering with the 'osmo_ecu_register()' function shall not be
+ * considered as a derivative work under any applicable copyright law;
+ * the copyleft terms of GPLv2 shall hence not apply to any such ECU
+ * implementation.
+ *
+ * The intent of the above exception is to allow anyone to combine third
+ * party Error Concealment Unit implementations with libosmocodec.
+ * including but not limited to such published by ETSI.
+ *
+ * -- Harald Welte <laforge@gnumonks.org> on August 1, 2019.
+ */
+
+/* Codec independent ECU state */
+struct osmo_ecu_state {
+ enum osmo_ecu_codec codec;
+ uint8_t data[0];
+};
+
+/* initialize an ECU instance */
+struct osmo_ecu_state *osmo_ecu_init(void *ctx, enum osmo_ecu_codec codec);
+
+/* destroy an ECU instance */
+void osmo_ecu_destroy(struct osmo_ecu_state *st);
+
+/* process a received frame a substitute/erroneous frame */
+int osmo_ecu_frame_in(struct osmo_ecu_state *st, bool bfi,
+ const uint8_t *frame, unsigned int frame_bytes);
+
+/* generate output data for a substitute/erroneous frame */
+int osmo_ecu_frame_out(struct osmo_ecu_state *st, uint8_t *frame_out);
+
+struct osmo_ecu_ops {
+ struct osmo_ecu_state * (*init)(void *ctx, enum osmo_ecu_codec codec);
+ void (*destroy)(struct osmo_ecu_state *);
+ int (*frame_in)(struct osmo_ecu_state *st, bool bfi,
+ const uint8_t *frame, unsigned int frame_bytes);
+ int (*frame_out)(struct osmo_ecu_state *st, uint8_t *frame_out);
+};
+
+int osmo_ecu_register(const struct osmo_ecu_ops *ops, enum osmo_ecu_codec codec);
diff --git a/include/osmocom/core/fsm.h b/include/osmocom/core/fsm.h
index 1701c45e..269befa5 100644
--- a/include/osmocom/core/fsm.h
+++ b/include/osmocom/core/fsm.h
@@ -122,6 +122,7 @@ struct osmo_fsm_inst {
void osmo_fsm_log_addr(bool log_addr);
void osmo_fsm_log_timeouts(bool log_timeouts);
void osmo_fsm_term_safely(bool term_safely);
+void osmo_fsm_set_dealloc_ctx(void *ctx);
/*! Log using FSM instance's context, on explicit logging subsystem and level.
* \param fi An osmo_fsm_inst.
diff --git a/include/osmocom/core/logging.h b/include/osmocom/core/logging.h
index 803b4a94..139d2916 100644
--- a/include/osmocom/core/logging.h
+++ b/include/osmocom/core/logging.h
@@ -154,6 +154,7 @@ enum log_ctx_index {
LOG_CTX_GB_BVC,
LOG_CTX_BSC_SUBSCR,
LOG_CTX_VLR_SUBSCR,
+ LOG_CTX_L1_SAPI,
_LOG_CTX_COUNT
};
@@ -166,6 +167,7 @@ enum log_filter_index {
LOG_FLT_GB_BVC,
LOG_FLT_BSC_SUBSCR,
LOG_FLT_VLR_SUBSCR,
+ LOG_FLT_L1_SAPI,
_LOG_FLT_COUNT
};
@@ -377,6 +379,19 @@ void log_add_target(struct log_target *target);
void log_del_target(struct log_target *target);
struct log_target *log_target_find(int type, const char *fname);
-extern struct llist_head osmo_log_target_list;
+
+void log_enable_multithread(void);
+
+void log_tgt_mutex_lock_impl(void);
+void log_tgt_mutex_unlock_impl(void);
+#define LOG_MTX_DEBUG 0
+#if LOG_MTX_DEBUG
+ #include <pthread.h>
+ #define log_tgt_mutex_lock() do { fprintf(stderr, "[%lu] %s:%d [%s] lock\n", pthread_self(), __FILE__, __LINE__, __func__); log_tgt_mutex_lock_impl(); } while (0)
+ #define log_tgt_mutex_unlock() do { fprintf(stderr, "[%lu] %s:%d [%s] unlock\n", pthread_self(), __FILE__, __LINE__, __func__); log_tgt_mutex_unlock_impl(); } while (0)
+#else
+ #define log_tgt_mutex_lock() log_tgt_mutex_lock_impl()
+ #define log_tgt_mutex_unlock() log_tgt_mutex_unlock_impl()
+#endif
/*! @} */
diff --git a/include/osmocom/core/logging_internal.h b/include/osmocom/core/logging_internal.h
index a510f83e..2e656603 100644
--- a/include/osmocom/core/logging_internal.h
+++ b/include/osmocom/core/logging_internal.h
@@ -7,8 +7,9 @@
#include <osmocom/core/utils.h>
extern void *tall_log_ctx;
-extern const struct log_info *osmo_log_info;
+extern struct log_info *osmo_log_info;
extern const struct value_string loglevel_strs[];
+extern struct llist_head osmo_log_target_list;
void assert_loginfo(const char *src);
diff --git a/include/osmocom/core/msgb.h b/include/osmocom/core/msgb.h
index e05d37f0..1833a6c1 100644
--- a/include/osmocom/core/msgb.h
+++ b/include/osmocom/core/msgb.h
@@ -518,7 +518,7 @@ static inline int msgb_l3trim(struct msgb *msg, int l3len)
static inline struct msgb *msgb_alloc_headroom_c(const void *ctx, int size, int headroom,
const char *name)
{
- osmo_static_assert(size > headroom, headroom_bigger);
+ osmo_static_assert(size >= headroom, headroom_bigger);
struct msgb *msg = msgb_alloc_c(ctx, size, name);
if (msg)
@@ -540,7 +540,7 @@ static inline struct msgb *msgb_alloc_headroom_c(const void *ctx, int size, int
static inline struct msgb *msgb_alloc_headroom(int size, int headroom,
const char *name)
{
- osmo_static_assert(size > headroom, headroom_bigger);
+ osmo_static_assert(size >= headroom, headroom_bigger);
struct msgb *msg = msgb_alloc(size, name);
if (msg)
diff --git a/include/osmocom/core/select.h b/include/osmocom/core/select.h
index e4787b09..92904e2f 100644
--- a/include/osmocom/core/select.h
+++ b/include/osmocom/core/select.h
@@ -51,6 +51,8 @@ int osmo_fd_register(struct osmo_fd *fd);
void osmo_fd_unregister(struct osmo_fd *fd);
void osmo_fd_close(struct osmo_fd *fd);
int osmo_select_main(int polling);
+int osmo_select_main_ctx(int polling);
+void osmo_select_init(void);
struct osmo_fd *osmo_fd_get_by_fd(int fd);
diff --git a/include/osmocom/core/sockaddr_str.h b/include/osmocom/core/sockaddr_str.h
index 253b755f..6dd428c7 100644
--- a/include/osmocom/core/sockaddr_str.h
+++ b/include/osmocom/core/sockaddr_str.h
@@ -61,10 +61,15 @@ struct osmo_sockaddr_str {
* struct osmo_sockaddr_str *my_sockaddr_str = ...;
* printf("got " OSMO_SOCKADDR_STR_FMT "\n", OSMO_SOCKADDR_STR_FMT_ARGS(my_sockaddr_str));
*/
-#define OSMO_SOCKADDR_STR_FMT "%s:%u"
-#define OSMO_SOCKADDR_STR_FMT_ARGS(R) ((R)->ip ? : ""), (R)->port
+#define OSMO_SOCKADDR_STR_FMT "%s%s%s:%u"
+#define OSMO_SOCKADDR_STR_FMT_ARGS(R) \
+ ((R) && (R)->af == AF_INET6)? "[" : "", \
+ (R)? (R)->ip : "NULL", \
+ ((R) && (R)->af == AF_INET6)? "]" : "", \
+ (R)? (R)->port : 0
bool osmo_sockaddr_str_is_set(const struct osmo_sockaddr_str *sockaddr_str);
+bool osmo_sockaddr_str_is_nonzero(const struct osmo_sockaddr_str *sockaddr_str);
int osmo_sockaddr_str_from_str(struct osmo_sockaddr_str *sockaddr_str, const char *ip, uint16_t port);
diff --git a/include/osmocom/core/socket.h b/include/osmocom/core/socket.h
index 37b1eaef..e26ca0d3 100644
--- a/include/osmocom/core/socket.h
+++ b/include/osmocom/core/socket.h
@@ -36,6 +36,9 @@ struct osmo_fd;
/*! use SO_REUSEADDR on UDP ports (required for multicast) */
#define OSMO_SOCK_F_UDP_REUSEADDR (1 << 5)
+/*! maximum number of local or remote addresses supported by an osmo_sock instance */
+#define OSMO_SOCK_MAX_ADDRS 32
+
int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,
const char *host, uint16_t port, unsigned int flags);
@@ -43,6 +46,10 @@ int osmo_sock_init2(uint16_t family, uint16_t type, uint8_t proto,
const char *local_host, uint16_t local_port,
const char *remote_host, uint16_t remote_port, unsigned int flags);
+int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto,
+ const char **local_hosts, size_t local_hosts_cnt, uint16_t local_port,
+ const char **remote_hosts, size_t remote_hosts_cnt, uint16_t remote_port, unsigned int flags);
+
int osmo_sock_init_ofd(struct osmo_fd *ofd, int family, int type, int proto,
const char *host, uint16_t port, unsigned int flags);
diff --git a/include/osmocom/core/talloc.h b/include/osmocom/core/talloc.h
index 191a463f..c68a56cf 100644
--- a/include/osmocom/core/talloc.h
+++ b/include/osmocom/core/talloc.h
@@ -1,5 +1,27 @@
-/*! \file talloc.h
- * Convenience wrapper. libosmocore used to ship its own internal copy of
- * talloc, before libtalloc became a standard component on most systems */
+/*! \file talloc.h */
#pragma once
#include <talloc.h>
+
+/*! per-thread talloc contexts. This works around the problem that talloc is not
+ * thread-safe. However, one can simply have a different set of talloc contexts for each
+ * thread, and ensure that allocations made on one thread are always only free'd on that
+ * very same thread.
+ * WARNING: Users must make sure they free() on the same thread as they allocate!! */
+struct osmo_talloc_contexts {
+ /*! global per-thread talloc context. */
+ void *global;
+ /*! volatile select-dispatch context. This context is completely free'd and
+ * re-created every time the main select loop in osmo_select_main() returns from
+ * select(2) and calls per-fd callback functions. This allows users of this
+ * facility to allocate temporary objects like string buffers, message buffers
+ * and the like which are automatically free'd when going into the next select()
+ * system call */
+ void *select;
+};
+
+extern __thread struct osmo_talloc_contexts *osmo_ctx;
+
+/* short-hand #defines for the osmo talloc contexts (OTC) that can be used to pass
+ * to the various _c functions like msgb_alloc_c() */
+#define OTC_GLOBAL (osmo_ctx->global)
+#define OTC_SELECT (osmo_ctx->select)
diff --git a/include/osmocom/core/tdef.h b/include/osmocom/core/tdef.h
index 566f5dd3..54819d95 100644
--- a/include/osmocom/core/tdef.h
+++ b/include/osmocom/core/tdef.h
@@ -77,6 +77,10 @@ struct osmo_tdef {
/*! Currently active timeout value, e.g. set by user config. This is the only mutable member: a user may
* configure the timeout value, but neither unit nor any other field. */
unsigned long val;
+ /*! Minimum timer value (in this tdef unit), checked if set (not zero). */
+ unsigned long min_val;
+ /*! Maximum timer value (in this tdef unit), checked if set (not zero). */
+ unsigned long max_val;
};
/*! Iterate an array of struct osmo_tdef, the last item should be fully zero, i.e. "{}".
@@ -97,6 +101,9 @@ void osmo_tdefs_reset(struct osmo_tdef *tdefs);
unsigned long osmo_tdef_get(const struct osmo_tdef *tdefs, int T, enum osmo_tdef_unit as_unit,
long val_if_not_present);
struct osmo_tdef *osmo_tdef_get_entry(struct osmo_tdef *tdefs, int T);
+int osmo_tdef_set(struct osmo_tdef *tdefs, int T, unsigned long val, enum osmo_tdef_unit val_unit);
+bool osmo_tdef_val_in_range(struct osmo_tdef *tdef, unsigned long new_val);
+int osmo_tdef_range_str_buf(char *buf, size_t buf_len, struct osmo_tdef *t);
/*! Using osmo_tdef for osmo_fsm_inst: array entry for a mapping of state numbers to timeout definitions.
* For a usage example, see osmo_tdef_get_state_timeout() and test_tdef_state_timeout() in tdef_test.c. */
diff --git a/include/osmocom/gprs/gprs_bssgp.h b/include/osmocom/gprs/gprs_bssgp.h
index 400c3e00..0f87333a 100644
--- a/include/osmocom/gprs/gprs_bssgp.h
+++ b/include/osmocom/gprs/gprs_bssgp.h
@@ -112,6 +112,8 @@ extern struct llist_head bssgp_bvc_ctxts;
struct bssgp_bvc_ctx *btsctx_by_raid_cid(const struct gprs_ra_id *raid, uint16_t cid);
/* Find a BTS context based on BVCI+NSEI tuple */
struct bssgp_bvc_ctx *btsctx_by_bvci_nsei(uint16_t bvci, uint16_t nsei);
+/* Free a given BTS context */
+void bssgp_bvc_ctx_free(struct bssgp_bvc_ctx *ctx);
#define BVC_F_BLOCKED 0x0001
diff --git a/include/osmocom/gsm/bts_features.h b/include/osmocom/gsm/bts_features.h
index e84f95b6..7ead0203 100644
--- a/include/osmocom/gsm/bts_features.h
+++ b/include/osmocom/gsm/bts_features.h
@@ -23,6 +23,7 @@ enum osmo_bts_features {
BTS_FEAT_SPEECH_F_EFR,
BTS_FEAT_SPEECH_F_AMR,
BTS_FEAT_SPEECH_H_AMR,
+ BTS_FEAT_ETWS_PN,
_NUM_BTS_FEAT
};
diff --git a/include/osmocom/gsm/gsm0502.h b/include/osmocom/gsm/gsm0502.h
index fe5cf7e1..c9901dfd 100644
--- a/include/osmocom/gsm/gsm0502.h
+++ b/include/osmocom/gsm/gsm0502.h
@@ -35,3 +35,15 @@ gsm0502_get_paging_group(uint64_t imsi, unsigned int bs_cc_chans,
unsigned int
gsm0502_calc_paging_group(struct gsm48_control_channel_descr *chan_desc, uint64_t imsi);
+
+enum gsm0502_fn_remap_channel {
+ FN_REMAP_TCH_F,
+ FN_REMAP_TCH_H0,
+ FN_REMAP_TCH_H1,
+ FN_REMAP_FACCH_F,
+ FN_REMAP_FACCH_H0,
+ FN_REMAP_FACCH_H1,
+ FN_REMAP_MAX,
+};
+
+uint32_t gsm0502_fn_remap(uint32_t fn, enum gsm0502_fn_remap_channel channel);
diff --git a/include/osmocom/gsm/gsm0808_utils.h b/include/osmocom/gsm/gsm0808_utils.h
index 76db2b6d..ccdf5ed9 100644
--- a/include/osmocom/gsm/gsm0808_utils.h
+++ b/include/osmocom/gsm/gsm0808_utils.h
@@ -95,6 +95,7 @@ void gsm0808_cell_id_from_cgi(struct gsm0808_cell_id *cid, enum CELL_IDENT id_di
int gsm0808_cell_id_to_cgi(struct osmo_cell_global_id *cgi, const struct gsm0808_cell_id *cid);
void gsm0808_msgb_put_cell_id_u(struct msgb *msg, enum CELL_IDENT id_discr, const union gsm0808_cell_id_u *u);
int gsm0808_decode_cell_id_u(union gsm0808_cell_id_u *out, enum CELL_IDENT discr, const uint8_t *buf, unsigned int len);
+int gsm0808_cell_id_size(enum CELL_IDENT discr);
uint8_t gsm0808_enc_cause(struct msgb *msg, uint16_t cause);
uint8_t gsm0808_enc_aoip_trasp_addr(struct msgb *msg,
diff --git a/include/osmocom/gsm/gsm_utils.h b/include/osmocom/gsm/gsm_utils.h
index 7a5da9a6..de634348 100644
--- a/include/osmocom/gsm/gsm_utils.h
+++ b/include/osmocom/gsm/gsm_utils.h
@@ -30,7 +30,6 @@
#include <osmocom/core/defs.h>
#include <osmocom/core/utils.h>
-#include <osmocom/gsm/protocol/gsm_04_08.h>
#define ADD_MODULO(sum, delta, modulo) do { \
if ((sum += delta) >= modulo) \
@@ -116,8 +115,7 @@ int gsm_septet_encode(uint8_t *result, const char *data);
uint8_t gsm_get_octet_len(const uint8_t sept_len);
int gsm_7bit_decode_n_hdr(char *decoded, size_t n, const uint8_t *user_data, uint8_t length, uint8_t ud_hdr_ind);
-unsigned int ms_class_gmsk_dbm(enum gsm_band band, int ms_class);
-
+int ms_class_gmsk_dbm(enum gsm_band band, int ms_class);
int ms_pwr_ctl_lvl(enum gsm_band band, unsigned int dbm);
int ms_pwr_dbm(enum gsm_band band, uint8_t lvl);
diff --git a/include/osmocom/gsm/gsup.h b/include/osmocom/gsm/gsup.h
index be856620..49ddb742 100644
--- a/include/osmocom/gsm/gsup.h
+++ b/include/osmocom/gsm/gsup.h
@@ -45,6 +45,7 @@
#include <osmocom/gsm/protocol/gsm_03_40.h>
#include <osmocom/gsm/protocol/gsm_04_08_gprs.h>
#include <osmocom/gsm/protocol/gsm_08_08.h>
+#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/crypt/auth.h>
#define OSMO_GSUP_PORT 4222
@@ -86,6 +87,8 @@ enum osmo_gsup_iei {
OSMO_GSUP_AUTS_IE = 0x26,
OSMO_GSUP_RES_IE = 0x27,
OSMO_GSUP_CN_DOMAIN_IE = 0x28,
+ OSMO_GSUP_SUPPORTED_RAT_TYPES_IE = 0x29, /* supported RAT types */
+ OSMO_GSUP_CURRENT_RAT_TYPE_IE = 0x2a, /* currently used RAT type */
OSMO_GSUP_SESSION_ID_IE = 0x30,
OSMO_GSUP_SESSION_STATE_IE = 0x31,
@@ -373,6 +376,10 @@ struct osmo_gsup_message {
/*! Session Management cause as of 3GPP TS 24.008 10.5.6.6 / Table 10.5.157. */
enum gsm48_gsm_cause cause_sm;
+
+ enum osmo_rat_type current_rat_type;
+ enum osmo_rat_type supported_rat_types[8]; /*!< arbitrary choice */
+ size_t supported_rat_types_len;
};
int osmo_gsup_decode(const uint8_t *data, size_t data_len,
diff --git a/include/osmocom/gsm/l1sap.h b/include/osmocom/gsm/l1sap.h
index 19cc87a3..3d2ad253 100644
--- a/include/osmocom/gsm/l1sap.h
+++ b/include/osmocom/gsm/l1sap.h
@@ -67,8 +67,8 @@ struct ph_rach_ind_param {
/* elements added on 2018-02-26 */
int8_t rssi; /*!< RSSI of RACH indication */
uint16_t ber10k; /*!< BER in units of 0.01% */
- int16_t acc_delay_256bits;/* !< Burst TA Offset in 1/256th bits */
- int16_t lqual_cb; /* !< Link quality in centiBel */
+ int16_t acc_delay_256bits;/*!< Burst TA Offset in 1/256th bits */
+ int16_t lqual_cb; /*!< Link quality in centiBel */
};
/*! for PH-[UNIT]DATA.{req,ind} | PH-RTS.ind */
@@ -79,10 +79,10 @@ struct ph_data_param {
int8_t rssi; /*!< RSSI of receivedindication */
uint16_t ber10k; /*!< BER in units of 0.01% */
union {
- int16_t ta_offs_qbits; /* !< Burst TA Offset in quarter bits */
+ int16_t ta_offs_qbits; /*!< Burst TA Offset in quarter bits */
int16_t ta_offs_256bits;/*!< timing advance offset (in 1/256th bits) */
};
- int16_t lqual_cb; /* !< Link quality in centiBel */
+ int16_t lqual_cb; /*!< Link quality in centiBel */
enum osmo_ph_pres_info_type pdch_presence_info; /*!< Info regarding presence/validity of header and data parts */
};
@@ -93,7 +93,7 @@ struct ph_tch_param {
int8_t rssi; /*!< RSSI of received indication */
uint8_t marker; /*!< RTP Marker bit (speech onset indicator) */
uint16_t ber10k; /*!< BER in units of 0.01% */
- int16_t lqual_cb; /* !< Link quality in centiBel */
+ int16_t lqual_cb; /*!< Link quality in centiBel */
};
/*! for PH-CONN.ind */
diff --git a/include/osmocom/gsm/mncc.h b/include/osmocom/gsm/mncc.h
index e5e96074..db70235c 100644
--- a/include/osmocom/gsm/mncc.h
+++ b/include/osmocom/gsm/mncc.h
@@ -10,10 +10,10 @@
/* Expanded fields from GSM TS 04.08, Table 10.5.102 */
struct gsm_mncc_bearer_cap {
- int transfer; /* Information Transfer Capability */
- int mode; /* Transfer Mode */
- int coding; /* Coding Standard */
- int radio; /* Radio Channel Requirement */
+ int transfer; /* Information Transfer Capability, see enum gsm48_bcap_itcap. */
+ int mode; /* Transfer Mode, see enum gsm48_bcap_tmod. */
+ int coding; /* Coding Standard, see enum gsm48_bcap_coding.*/
+ int radio; /* Radio Channel Requirement, see enum gsm48_bcap_rrq. */
int speech_ctm; /* CTM text telephony indication */
int speech_ver[8]; /* Speech version indication, see enum gsm48_bcap_speech_ver; -1 marks end */
struct {
diff --git a/include/osmocom/gsm/protocol/gsm_04_08.h b/include/osmocom/gsm/protocol/gsm_04_08.h
index 2be6ed34..8370eca1 100644
--- a/include/osmocom/gsm/protocol/gsm_04_08.h
+++ b/include/osmocom/gsm/protocol/gsm_04_08.h
@@ -9,6 +9,8 @@
#include <osmocom/core/utils.h>
#include <osmocom/core/endian.h>
+#include <osmocom/gsm/gsm_utils.h>
+
struct gsm_lchan;
/* Chapter 10.5.1.5 */
@@ -73,7 +75,7 @@ const char *osmo_gsm48_classmark_a5_name(const struct osmo_gsm48_classmark *cm);
char *osmo_gsm48_classmark_a5_name_buf(char *buf, size_t buf_len, const struct osmo_gsm48_classmark *cm);
char *osmo_gsm48_classmark_a5_name_c(const void *ctx, const struct osmo_gsm48_classmark *cm);
void osmo_gsm48_classmark_update(struct osmo_gsm48_classmark *dst, const struct osmo_gsm48_classmark *src);
-
+int8_t osmo_gsm48_rfpowercap2powerclass(enum gsm_band band, uint8_t rf_power_cap);
/* Chapter 10.5.2.1b.3 */
#if OSMO_IS_LITTLE_ENDIAN == 1
struct gsm48_range_1024 {
@@ -887,13 +889,25 @@ struct gsm48_pag_resp {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t spare:4,
key_seq:4;
- uint32_t classmark2;
+ union {
+ uint32_t classmark2; /* Backward compatibility */
+ struct {
+ uint8_t cm2_len;
+ struct gsm48_classmark2 cm2;
+ };
+ };
uint8_t mi_len;
uint8_t mi[0];
#elif OSMO_IS_BIG_ENDIAN
/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
uint8_t key_seq:4, spare:4;
- uint32_t classmark2;
+ union {
+ uint32_t classmark2; /* Backward compatibility */
+ struct {
+ uint8_t cm2_len;
+ struct gsm48_classmark2 cm2;
+ };
+ };
uint8_t mi_len;
uint8_t mi[0];
#endif
@@ -954,7 +968,7 @@ struct gsm48_system_information_type_header {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t l2_plen;
uint8_t rr_protocol_discriminator :4,
- skip_indicator:4;
+ skip_indicator:4;
uint8_t system_information;
#elif OSMO_IS_BIG_ENDIAN
/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
@@ -1025,15 +1039,26 @@ struct gsm48_service_request {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t cm_service_type : 4,
cipher_key_seq : 4;
- /* length + 3 bytes */
- uint32_t classmark;
+ union {
+ uint32_t classmark; /* Backward compatibility */
+ struct {
+ uint8_t cm2_len;
+ struct gsm48_classmark2 classmark2;
+ };
+ };
uint8_t mi_len;
uint8_t mi[0];
/* optional priority level */
#elif OSMO_IS_BIG_ENDIAN
/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianess.py) */
uint8_t cipher_key_seq:4, cm_service_type:4;
- uint32_t classmark;
+ union {
+ uint32_t classmark; /* Backward compatibility */
+ struct {
+ uint8_t cm2_len;
+ struct gsm48_classmark2 classmark2;
+ };
+ };
uint8_t mi_len;
uint8_t mi[0];
#endif
@@ -1104,7 +1129,7 @@ struct gsm48_system_information_type_4 {
struct gsm48_system_information_type_5 {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t rr_protocol_discriminator :4,
- skip_indicator:4;
+ skip_indicator:4;
uint8_t system_information;
uint8_t bcch_frequency_list[16];
#elif OSMO_IS_BIG_ENDIAN
@@ -1149,7 +1174,7 @@ struct gsm48_system_information_type_5ter {
struct gsm48_system_information_type_6 {
#if OSMO_IS_LITTLE_ENDIAN
uint8_t rr_protocol_discriminator :4,
- skip_indicator:4;
+ skip_indicator:4;
uint8_t system_information;
uint16_t cell_identity;
struct gsm48_loc_area_id lai;
diff --git a/include/osmocom/gsm/protocol/gsm_08_08.h b/include/osmocom/gsm/protocol/gsm_08_08.h
index 9806e083..a5406095 100644
--- a/include/osmocom/gsm/protocol/gsm_08_08.h
+++ b/include/osmocom/gsm/protocol/gsm_08_08.h
@@ -512,13 +512,15 @@ enum gsm0808_paging_info {
GSM0808_PAGINF_FOR_USSD = 0x02,
};
-/* 3GPP TS 48.008 3.2.2.104 Speech Codec */
+/*! 3GPP TS 48.008 3.2.2.104 Speech Codec */
struct gsm0808_speech_codec {
bool fi;
bool pi;
bool pt;
bool tf;
+ /*! See enum gsm0808_speech_codec_type. */
uint8_t type;
+ /*! For examples, see enum gsm0808_speech_codec_defaults. */
uint16_t cfg;
};
diff --git a/include/osmocom/gsm/protocol/gsm_08_58.h b/include/osmocom/gsm/protocol/gsm_08_58.h
index 1d4530d1..da55a8d9 100644
--- a/include/osmocom/gsm/protocol/gsm_08_58.h
+++ b/include/osmocom/gsm/protocol/gsm_08_58.h
@@ -236,6 +236,8 @@ enum abis_rsl_msgtype {
RSL_MT_IPAC_DLCX = 0x77,
RSL_MT_IPAC_DLCX_ACK,
RSL_MT_IPAC_DLCX_NACK,
+
+ RSL_MT_OSMO_ETWS_CMD = 0x7f,
};
/*! Siemens vendor-specific RSL message types */
diff --git a/include/osmocom/vty/tdef_vty.h b/include/osmocom/vty/tdef_vty.h
index 3027913d..6334d5ba 100644
--- a/include/osmocom/vty/tdef_vty.h
+++ b/include/osmocom/vty/tdef_vty.h
@@ -25,6 +25,9 @@
#pragma once
#include <stdint.h>
+#include <stdarg.h>
+
+#include <osmocom/vty/command.h>
struct vty;
diff --git a/libosmocore.pc.in b/libosmocore.pc.in
index d355659b..80f17c8b 100644
--- a/libosmocore.pc.in
+++ b/libosmocore.pc.in
@@ -7,5 +7,5 @@ Name: Osmocom Core Library
Description: C Utility Library
Version: @VERSION@
Libs: -L${libdir} @TALLOC_LIBS@ -losmocore
-Cflags: -I${includedir}/ @TALLOC_CFLAGS@
-
+Libs.private: @PTHREAD_LIBS@ @LIBSCTP_LIBS@
+Cflags: -I${includedir}/ @TALLOC_CFLAGS@ @PTHREAD_CFLAGS@
diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4
new file mode 100644
index 00000000..4920e073
--- /dev/null
+++ b/m4/ax_pthread.m4
@@ -0,0 +1,486 @@
+# ===========================================================================
+# https://www.gnu.org/software/autoconf-archive/ax_pthread.html
+# ===========================================================================
+#
+# SYNOPSIS
+#
+# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]])
+#
+# DESCRIPTION
+#
+# This macro figures out how to build C programs using POSIX threads. It
+# sets the PTHREAD_LIBS output variable to the threads library and linker
+# flags, and the PTHREAD_CFLAGS output variable to any special C compiler
+# flags that are needed. (The user can also force certain compiler
+# flags/libs to be tested by setting these environment variables.)
+#
+# Also sets PTHREAD_CC to any special C compiler that is needed for
+# multi-threaded programs (defaults to the value of CC otherwise). (This
+# is necessary on AIX to use the special cc_r compiler alias.)
+#
+# NOTE: You are assumed to not only compile your program with these flags,
+# but also to link with them as well. For example, you might link with
+# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS
+#
+# If you are only building threaded programs, you may wish to use these
+# variables in your default LIBS, CFLAGS, and CC:
+#
+# LIBS="$PTHREAD_LIBS $LIBS"
+# CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+# CC="$PTHREAD_CC"
+#
+# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant
+# has a nonstandard name, this macro defines PTHREAD_CREATE_JOINABLE to
+# that name (e.g. PTHREAD_CREATE_UNDETACHED on AIX).
+#
+# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the
+# PTHREAD_PRIO_INHERIT symbol is defined when compiling with
+# PTHREAD_CFLAGS.
+#
+# ACTION-IF-FOUND is a list of shell commands to run if a threads library
+# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it
+# is not found. If ACTION-IF-FOUND is not specified, the default action
+# will define HAVE_PTHREAD.
+#
+# Please let the authors know if this macro fails on any platform, or if
+# you have any other suggestions or comments. This macro was based on work
+# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help
+# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by
+# Alejandro Forero Cuervo to the autoconf macro repository. We are also
+# grateful for the helpful feedback of numerous users.
+#
+# Updated for Autoconf 2.68 by Daniel Richard G.
+#
+# LICENSE
+#
+# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu>
+# Copyright (c) 2011 Daniel Richard G. <skunk@iSKUNK.ORG>
+#
+# This program is free software: you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by the
+# Free Software Foundation, either version 3 of the License, or (at your
+# option) any later version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
+# Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program. If not, see <https://www.gnu.org/licenses/>.
+#
+# As a special exception, the respective Autoconf Macro's copyright owner
+# gives unlimited permission to copy, distribute and modify the configure
+# scripts that are the output of Autoconf when processing the Macro. You
+# need not follow the terms of the GNU General Public License when using
+# or distributing such scripts, even though portions of the text of the
+# Macro appear in them. The GNU General Public License (GPL) does govern
+# all other use of the material that constitutes the Autoconf Macro.
+#
+# This special exception to the GPL applies to versions of the Autoconf
+# Macro released by the Autoconf Archive. When you make and distribute a
+# modified version of the Autoconf Macro, you may extend this special
+# exception to the GPL to apply to your modified version as well.
+
+#serial 25
+
+AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD])
+AC_DEFUN([AX_PTHREAD], [
+AC_REQUIRE([AC_CANONICAL_HOST])
+AC_REQUIRE([AC_PROG_CC])
+AC_REQUIRE([AC_PROG_SED])
+AC_LANG_PUSH([C])
+ax_pthread_ok=no
+
+# We used to check for pthread.h first, but this fails if pthread.h
+# requires special compiler flags (e.g. on Tru64 or Sequent).
+# It gets checked for in the link test anyway.
+
+# First of all, check if the user has set any of the PTHREAD_LIBS,
+# etcetera environment variables, and if threads linking works using
+# them:
+if test "x$PTHREAD_CFLAGS$PTHREAD_LIBS" != "x"; then
+ ax_pthread_save_CC="$CC"
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ ax_pthread_save_LIBS="$LIBS"
+ AS_IF([test "x$PTHREAD_CC" != "x"], [CC="$PTHREAD_CC"])
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+ AC_MSG_CHECKING([for pthread_join using $CC $PTHREAD_CFLAGS $PTHREAD_LIBS])
+ AC_LINK_IFELSE([AC_LANG_CALL([], [pthread_join])], [ax_pthread_ok=yes])
+ AC_MSG_RESULT([$ax_pthread_ok])
+ if test "x$ax_pthread_ok" = "xno"; then
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+ fi
+ CC="$ax_pthread_save_CC"
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ LIBS="$ax_pthread_save_LIBS"
+fi
+
+# We must check for the threads library under a number of different
+# names; the ordering is very important because some systems
+# (e.g. DEC) have both -lpthread and -lpthreads, where one of the
+# libraries is broken (non-POSIX).
+
+# Create a list of thread flags to try. Items starting with a "-" are
+# C compiler flags, and other items are library names, except for "none"
+# which indicates that we try without any flags at all, and "pthread-config"
+# which is a program returning the flags for the Pth emulation library.
+
+ax_pthread_flags="pthreads none -Kthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config"
+
+# The ordering *is* (sometimes) important. Some notes on the
+# individual items follow:
+
+# pthreads: AIX (must check this before -lpthread)
+# none: in case threads are in libc; should be tried before -Kthread and
+# other compiler flags to prevent continual compiler warnings
+# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h)
+# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads), Tru64
+# (Note: HP C rejects this with "bad form for `-t' option")
+# -pthreads: Solaris/gcc (Note: HP C also rejects)
+# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it
+# doesn't hurt to check since this sometimes defines pthreads and
+# -D_REENTRANT too), HP C (must be checked before -lpthread, which
+# is present but should not be used directly; and before -mthreads,
+# because the compiler interprets this as "-mt" + "-hreads")
+# -mthreads: Mingw32/gcc, Lynx/gcc
+# pthread: Linux, etcetera
+# --thread-safe: KAI C++
+# pthread-config: use pthread-config program (for GNU Pth library)
+
+case $host_os in
+
+ freebsd*)
+
+ # -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able)
+ # lthread: LinuxThreads port on FreeBSD (also preferred to -pthread)
+
+ ax_pthread_flags="-kthread lthread $ax_pthread_flags"
+ ;;
+
+ hpux*)
+
+ # From the cc(1) man page: "[-mt] Sets various -D flags to enable
+ # multi-threading and also sets -lpthread."
+
+ ax_pthread_flags="-mt -pthread pthread $ax_pthread_flags"
+ ;;
+
+ openedition*)
+
+ # IBM z/OS requires a feature-test macro to be defined in order to
+ # enable POSIX threads at all, so give the user a hint if this is
+ # not set. (We don't define these ourselves, as they can affect
+ # other portions of the system API in unpredictable ways.)
+
+ AC_EGREP_CPP([AX_PTHREAD_ZOS_MISSING],
+ [
+# if !defined(_OPEN_THREADS) && !defined(_UNIX03_THREADS)
+ AX_PTHREAD_ZOS_MISSING
+# endif
+ ],
+ [AC_MSG_WARN([IBM z/OS requires -D_OPEN_THREADS or -D_UNIX03_THREADS to enable pthreads support.])])
+ ;;
+
+ solaris*)
+
+ # On Solaris (at least, for some versions), libc contains stubbed
+ # (non-functional) versions of the pthreads routines, so link-based
+ # tests will erroneously succeed. (N.B.: The stubs are missing
+ # pthread_cleanup_push, or rather a function called by this macro,
+ # so we could check for that, but who knows whether they'll stub
+ # that too in a future libc.) So we'll check first for the
+ # standard Solaris way of linking pthreads (-mt -lpthread).
+
+ ax_pthread_flags="-mt,pthread pthread $ax_pthread_flags"
+ ;;
+esac
+
+# GCC generally uses -pthread, or -pthreads on some platforms (e.g. SPARC)
+
+AS_IF([test "x$GCC" = "xyes"],
+ [ax_pthread_flags="-pthread -pthreads $ax_pthread_flags"])
+
+# The presence of a feature test macro requesting re-entrant function
+# definitions is, on some systems, a strong hint that pthreads support is
+# correctly enabled
+
+case $host_os in
+ darwin* | hpux* | linux* | osf* | solaris*)
+ ax_pthread_check_macro="_REENTRANT"
+ ;;
+
+ aix*)
+ ax_pthread_check_macro="_THREAD_SAFE"
+ ;;
+
+ *)
+ ax_pthread_check_macro="--"
+ ;;
+esac
+AS_IF([test "x$ax_pthread_check_macro" = "x--"],
+ [ax_pthread_check_cond=0],
+ [ax_pthread_check_cond="!defined($ax_pthread_check_macro)"])
+
+# Are we compiling with Clang?
+
+AC_CACHE_CHECK([whether $CC is Clang],
+ [ax_cv_PTHREAD_CLANG],
+ [ax_cv_PTHREAD_CLANG=no
+ # Note that Autoconf sets GCC=yes for Clang as well as GCC
+ if test "x$GCC" = "xyes"; then
+ AC_EGREP_CPP([AX_PTHREAD_CC_IS_CLANG],
+ [/* Note: Clang 2.7 lacks __clang_[a-z]+__ */
+# if defined(__clang__) && defined(__llvm__)
+ AX_PTHREAD_CC_IS_CLANG
+# endif
+ ],
+ [ax_cv_PTHREAD_CLANG=yes])
+ fi
+ ])
+ax_pthread_clang="$ax_cv_PTHREAD_CLANG"
+
+ax_pthread_clang_warning=no
+
+# Clang needs special handling, because older versions handle the -pthread
+# option in a rather... idiosyncratic way
+
+if test "x$ax_pthread_clang" = "xyes"; then
+
+ # Clang takes -pthread; it has never supported any other flag
+
+ # (Note 1: This will need to be revisited if a system that Clang
+ # supports has POSIX threads in a separate library. This tends not
+ # to be the way of modern systems, but it's conceivable.)
+
+ # (Note 2: On some systems, notably Darwin, -pthread is not needed
+ # to get POSIX threads support; the API is always present and
+ # active. We could reasonably leave PTHREAD_CFLAGS empty. But
+ # -pthread does define _REENTRANT, and while the Darwin headers
+ # ignore this macro, third-party headers might not.)
+
+ PTHREAD_CFLAGS="-pthread"
+ PTHREAD_LIBS=
+
+ ax_pthread_ok=yes
+
+ # However, older versions of Clang make a point of warning the user
+ # that, in an invocation where only linking and no compilation is
+ # taking place, the -pthread option has no effect ("argument unused
+ # during compilation"). They expect -pthread to be passed in only
+ # when source code is being compiled.
+ #
+ # Problem is, this is at odds with the way Automake and most other
+ # C build frameworks function, which is that the same flags used in
+ # compilation (CFLAGS) are also used in linking. Many systems
+ # supported by AX_PTHREAD require exactly this for POSIX threads
+ # support, and in fact it is often not straightforward to specify a
+ # flag that is used only in the compilation phase and not in
+ # linking. Such a scenario is extremely rare in practice.
+ #
+ # Even though use of the -pthread flag in linking would only print
+ # a warning, this can be a nuisance for well-run software projects
+ # that build with -Werror. So if the active version of Clang has
+ # this misfeature, we search for an option to squash it.
+
+ AC_CACHE_CHECK([whether Clang needs flag to prevent "argument unused" warning when linking with -pthread],
+ [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG],
+ [ax_cv_PTHREAD_CLANG_NO_WARN_FLAG=unknown
+ # Create an alternate version of $ac_link that compiles and
+ # links in two steps (.c -> .o, .o -> exe) instead of one
+ # (.c -> exe), because the warning occurs only in the second
+ # step
+ ax_pthread_save_ac_link="$ac_link"
+ ax_pthread_sed='s/conftest\.\$ac_ext/conftest.$ac_objext/g'
+ ax_pthread_link_step=`$as_echo "$ac_link" | sed "$ax_pthread_sed"`
+ ax_pthread_2step_ac_link="($ac_compile) && (echo ==== >&5) && ($ax_pthread_link_step)"
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ for ax_pthread_try in '' -Qunused-arguments -Wno-unused-command-line-argument unknown; do
+ AS_IF([test "x$ax_pthread_try" = "xunknown"], [break])
+ CFLAGS="-Werror -Wunknown-warning-option $ax_pthread_try -pthread $ax_pthread_save_CFLAGS"
+ ac_link="$ax_pthread_save_ac_link"
+ AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])],
+ [ac_link="$ax_pthread_2step_ac_link"
+ AC_LINK_IFELSE([AC_LANG_SOURCE([[int main(void){return 0;}]])],
+ [break])
+ ])
+ done
+ ac_link="$ax_pthread_save_ac_link"
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ AS_IF([test "x$ax_pthread_try" = "x"], [ax_pthread_try=no])
+ ax_cv_PTHREAD_CLANG_NO_WARN_FLAG="$ax_pthread_try"
+ ])
+
+ case "$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG" in
+ no | unknown) ;;
+ *) PTHREAD_CFLAGS="$ax_cv_PTHREAD_CLANG_NO_WARN_FLAG $PTHREAD_CFLAGS" ;;
+ esac
+
+fi # $ax_pthread_clang = yes
+
+if test "x$ax_pthread_ok" = "xno"; then
+for ax_pthread_try_flag in $ax_pthread_flags; do
+
+ case $ax_pthread_try_flag in
+ none)
+ AC_MSG_CHECKING([whether pthreads work without any flags])
+ ;;
+
+ -mt,pthread)
+ AC_MSG_CHECKING([whether pthreads work with -mt -lpthread])
+ PTHREAD_CFLAGS="-mt"
+ PTHREAD_LIBS="-lpthread"
+ ;;
+
+ -*)
+ AC_MSG_CHECKING([whether pthreads work with $ax_pthread_try_flag])
+ PTHREAD_CFLAGS="$ax_pthread_try_flag"
+ ;;
+
+ pthread-config)
+ AC_CHECK_PROG([ax_pthread_config], [pthread-config], [yes], [no])
+ AS_IF([test "x$ax_pthread_config" = "xno"], [continue])
+ PTHREAD_CFLAGS="`pthread-config --cflags`"
+ PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`"
+ ;;
+
+ *)
+ AC_MSG_CHECKING([for the pthreads library -l$ax_pthread_try_flag])
+ PTHREAD_LIBS="-l$ax_pthread_try_flag"
+ ;;
+ esac
+
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ ax_pthread_save_LIBS="$LIBS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+
+ # Check for various functions. We must include pthread.h,
+ # since some functions may be macros. (On the Sequent, we
+ # need a special flag -Kthread to make this header compile.)
+ # We check for pthread_join because it is in -lpthread on IRIX
+ # while pthread_create is in libc. We check for pthread_attr_init
+ # due to DEC craziness with -lpthreads. We check for
+ # pthread_cleanup_push because it is one of the few pthread
+ # functions on Solaris that doesn't have a non-functional libc stub.
+ # We try pthread_create on general principles.
+
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>
+# if $ax_pthread_check_cond
+# error "$ax_pthread_check_macro must be defined"
+# endif
+ static void routine(void *a) { a = 0; }
+ static void *start_routine(void *a) { return a; }],
+ [pthread_t th; pthread_attr_t attr;
+ pthread_create(&th, 0, start_routine, 0);
+ pthread_join(th, 0);
+ pthread_attr_init(&attr);
+ pthread_cleanup_push(routine, 0);
+ pthread_cleanup_pop(0) /* ; */])],
+ [ax_pthread_ok=yes],
+ [])
+
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ LIBS="$ax_pthread_save_LIBS"
+
+ AC_MSG_RESULT([$ax_pthread_ok])
+ AS_IF([test "x$ax_pthread_ok" = "xyes"], [break])
+
+ PTHREAD_LIBS=""
+ PTHREAD_CFLAGS=""
+done
+fi
+
+# Various other checks:
+if test "x$ax_pthread_ok" = "xyes"; then
+ ax_pthread_save_CFLAGS="$CFLAGS"
+ ax_pthread_save_LIBS="$LIBS"
+ CFLAGS="$CFLAGS $PTHREAD_CFLAGS"
+ LIBS="$PTHREAD_LIBS $LIBS"
+
+ # Detect AIX lossage: JOINABLE attribute is called UNDETACHED.
+ AC_CACHE_CHECK([for joinable pthread attribute],
+ [ax_cv_PTHREAD_JOINABLE_ATTR],
+ [ax_cv_PTHREAD_JOINABLE_ATTR=unknown
+ for ax_pthread_attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do
+ AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>],
+ [int attr = $ax_pthread_attr; return attr /* ; */])],
+ [ax_cv_PTHREAD_JOINABLE_ATTR=$ax_pthread_attr; break],
+ [])
+ done
+ ])
+ AS_IF([test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xunknown" && \
+ test "x$ax_cv_PTHREAD_JOINABLE_ATTR" != "xPTHREAD_CREATE_JOINABLE" && \
+ test "x$ax_pthread_joinable_attr_defined" != "xyes"],
+ [AC_DEFINE_UNQUOTED([PTHREAD_CREATE_JOINABLE],
+ [$ax_cv_PTHREAD_JOINABLE_ATTR],
+ [Define to necessary symbol if this constant
+ uses a non-standard name on your system.])
+ ax_pthread_joinable_attr_defined=yes
+ ])
+
+ AC_CACHE_CHECK([whether more special flags are required for pthreads],
+ [ax_cv_PTHREAD_SPECIAL_FLAGS],
+ [ax_cv_PTHREAD_SPECIAL_FLAGS=no
+ case $host_os in
+ solaris*)
+ ax_cv_PTHREAD_SPECIAL_FLAGS="-D_POSIX_PTHREAD_SEMANTICS"
+ ;;
+ esac
+ ])
+ AS_IF([test "x$ax_cv_PTHREAD_SPECIAL_FLAGS" != "xno" && \
+ test "x$ax_pthread_special_flags_added" != "xyes"],
+ [PTHREAD_CFLAGS="$ax_cv_PTHREAD_SPECIAL_FLAGS $PTHREAD_CFLAGS"
+ ax_pthread_special_flags_added=yes])
+
+ AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT],
+ [ax_cv_PTHREAD_PRIO_INHERIT],
+ [AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <pthread.h>]],
+ [[int i = PTHREAD_PRIO_INHERIT;
+ return i;]])],
+ [ax_cv_PTHREAD_PRIO_INHERIT=yes],
+ [ax_cv_PTHREAD_PRIO_INHERIT=no])
+ ])
+ AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes" && \
+ test "x$ax_pthread_prio_inherit_defined" != "xyes"],
+ [AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], [1], [Have PTHREAD_PRIO_INHERIT.])
+ ax_pthread_prio_inherit_defined=yes
+ ])
+
+ CFLAGS="$ax_pthread_save_CFLAGS"
+ LIBS="$ax_pthread_save_LIBS"
+
+ # More AIX lossage: compile with *_r variant
+ if test "x$GCC" != "xyes"; then
+ case $host_os in
+ aix*)
+ AS_CASE(["x/$CC"],
+ [x*/c89|x*/c89_128|x*/c99|x*/c99_128|x*/cc|x*/cc128|x*/xlc|x*/xlc_v6|x*/xlc128|x*/xlc128_v6],
+ [#handle absolute path differently from PATH based program lookup
+ AS_CASE(["x$CC"],
+ [x/*],
+ [AS_IF([AS_EXECUTABLE_P([${CC}_r])],[PTHREAD_CC="${CC}_r"])],
+ [AC_CHECK_PROGS([PTHREAD_CC],[${CC}_r],[$CC])])])
+ ;;
+ esac
+ fi
+fi
+
+test -n "$PTHREAD_CC" || PTHREAD_CC="$CC"
+
+AC_SUBST([PTHREAD_LIBS])
+AC_SUBST([PTHREAD_CFLAGS])
+AC_SUBST([PTHREAD_CC])
+
+# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND:
+if test "x$ax_pthread_ok" = "xyes"; then
+ ifelse([$1],,[AC_DEFINE([HAVE_PTHREAD],[1],[Define if you have POSIX threads libraries and header files.])],[$1])
+ :
+else
+ ax_pthread_ok=no
+ $2
+fi
+AC_LANG_POP
+])dnl AX_PTHREAD
diff --git a/osmo-release.sh b/osmo-release.sh
index 1effe7e2..44504360 100755
--- a/osmo-release.sh
+++ b/osmo-release.sh
@@ -156,16 +156,18 @@ if [ "z$LIBVERS" != "z" ]; then
if [ "z$DRY_RUN" != "z0" ]; then
exit 0
fi
- if [ -f "TODO-RELEASE" ]; then
- grep '#' TODO-RELEASE > TODO-RELEASE.clean
- mv TODO-RELEASE.clean TODO-RELEASE
- git add TODO-RELEASE
- fi
fi
if [ "z$DRY_RUN" != "z0" ]; then
exit 0
fi
+
+if [ -f "TODO-RELEASE" ]; then
+ grep '#' TODO-RELEASE > TODO-RELEASE.clean
+ mv TODO-RELEASE.clean TODO-RELEASE
+ git add TODO-RELEASE
+fi
+
gbp dch --debian-tag='%(version)s' --auto --meta --git-author --multimaint-merge --ignore-branch --new-version="$NEW_VER"
dch -r -m --distribution "unstable" ""
git add ${GIT_TOPDIR}/debian/changelog
diff --git a/src/Makefile.am b/src/Makefile.am
index f9378100..99432811 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,7 +4,7 @@
LIBVERSION=14:0:2
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
-AM_CFLAGS = -Wall $(TALLOC_CFLAGS)
+AM_CFLAGS = -Wall $(TALLOC_CFLAGS) $(PTHREAD_CFLAGS) $(LIBSCTP_CFLAGS)
if ENABLE_PSEUDOTALLOC
AM_CPPFLAGS += -I$(top_srcdir)/src/pseudotalloc
@@ -12,8 +12,8 @@ endif
lib_LTLIBRARIES = libosmocore.la
-libosmocore_la_LIBADD = $(BACKTRACE_LIB) $(TALLOC_LIBS) $(LIBRARY_RT)
-libosmocore_la_SOURCES = timer.c timer_gettimeofday.c timer_clockgettime.c \
+libosmocore_la_LIBADD = $(BACKTRACE_LIB) $(TALLOC_LIBS) $(LIBRARY_RT) $(PTHREAD_LIBS) $(LIBSCTP_LIBS)
+libosmocore_la_SOURCES = context.c timer.c timer_gettimeofday.c timer_clockgettime.c \
select.c signal.c msgb.c bits.c \
bitvec.c bitcomp.c counter.c fsm.c \
write_queue.c utils.c socket.c \
diff --git a/src/codec/Makefile.am b/src/codec/Makefile.am
index b522d43a..c9d7a228 100644
--- a/src/codec/Makefile.am
+++ b/src/codec/Makefile.am
@@ -13,6 +13,6 @@ endif
lib_LTLIBRARIES = libosmocodec.la
-libosmocodec_la_SOURCES = gsm610.c gsm620.c gsm660.c gsm690.c ecu_fr.c
+libosmocodec_la_SOURCES = gsm610.c gsm620.c gsm660.c gsm690.c ecu.c ecu_fr.c
libosmocodec_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined
libosmocodec_la_LIBADD = $(top_builddir)/src/libosmocore.la
diff --git a/src/codec/ecu.c b/src/codec/ecu.c
new file mode 100644
index 00000000..db7148ce
--- /dev/null
+++ b/src/codec/ecu.c
@@ -0,0 +1,118 @@
+/* Core infrastructure for ECU implementations */
+
+/* (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ *
+ * All Rights Reserved
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+/* As the developer and copyright holder of the related code, I hereby
+ * state that any ECU implementation using 'struct osmo_ecu_ops' and
+ * registering with the 'osmo_ecu_register()' function shall not be
+ * considered as a derivative work under any applicable copyright law;
+ * the copyleft terms of GPLv2 shall hence not apply to any such ECU
+ * implementation.
+ *
+ * The intent of the above exception is to allow anyone to combine third
+ * party Error Concealment Unit implementations with libosmocodec.
+ * including but not limited to such published by ETSI.
+ *
+ * -- Harald Welte <laforge@gnumonks.org> on August 1, 2019.
+ */
+
+#include <string.h>
+#include <errno.h>
+
+#include <osmocom/codec/ecu.h>
+#include <osmocom/core/talloc.h>
+
+static const struct osmo_ecu_ops *g_ecu_ops[_NUM_OSMO_ECU_CODECS];
+
+/***********************************************************************
+ * high-level API for users
+ ***********************************************************************/
+
+/*! initialize an ECU instance for given codec.
+ * \param[in] ctx talloc context from which to allocate
+ * \parma[in] codec codec for which to initialize/create ECU */
+struct osmo_ecu_state *osmo_ecu_init(void *ctx, enum osmo_ecu_codec codec)
+{
+ if (codec >= ARRAY_SIZE(g_ecu_ops))
+ return NULL;
+ if (!g_ecu_ops[codec] || !g_ecu_ops[codec]->init)
+ return NULL;
+ return g_ecu_ops[codec]->init(ctx, codec);
+}
+
+/*! destroy an ECU instance */
+void osmo_ecu_destroy(struct osmo_ecu_state *st)
+{
+ if (st->codec >= ARRAY_SIZE(g_ecu_ops))
+ return;
+ if (!g_ecu_ops[st->codec])
+ return;
+
+ if (!g_ecu_ops[st->codec]->destroy)
+ talloc_free(st);
+ else
+ g_ecu_ops[st->codec]->destroy(st);
+}
+
+/*! process a received frame a substitute/erroneous frame.
+ * \param[in] st ECU state/instance on which to operate
+ * \param[in] bfi Bad Frame Indication
+ * \param[in] frame received codec frame to be processed
+ * \param[in] frame_bytes number of bytes available in frame */
+int osmo_ecu_frame_in(struct osmo_ecu_state *st, bool bfi,
+ const uint8_t *frame, unsigned int frame_bytes)
+{
+ if (st->codec >= ARRAY_SIZE(g_ecu_ops))
+ return -EINVAL;
+ if (!g_ecu_ops[st->codec])
+ return -EBUSY;
+ return g_ecu_ops[st->codec]->frame_in(st, bfi, frame, frame_bytes);
+}
+
+/*! generate output data for a substitute/erroneous frame.
+ * \param[in] st ECU state/instance on which to operate
+ * \param[out] frame_out buffer for generated output frame
+ * \return number of bytes written to frame_out; negative on error */
+int osmo_ecu_frame_out(struct osmo_ecu_state *st, uint8_t *frame_out)
+{
+ if (st->codec >= ARRAY_SIZE(g_ecu_ops))
+ return -EINVAL;
+ if (!g_ecu_ops[st->codec])
+ return -EBUSY;
+ return g_ecu_ops[st->codec]->frame_out(st, frame_out);
+}
+
+/***********************************************************************
+ * low-level API for ECU implementations
+ ***********************************************************************/
+
+/*! register an ECU implementation for a given codec */
+int osmo_ecu_register(const struct osmo_ecu_ops *ops, enum osmo_ecu_codec codec)
+{
+ if (codec >= ARRAY_SIZE(g_ecu_ops))
+ return -EINVAL;
+ if (g_ecu_ops[codec])
+ return -EBUSY;
+
+ g_ecu_ops[codec] = ops;
+
+ return 0;
+}
diff --git a/src/codec/ecu_fr.c b/src/codec/ecu_fr.c
index ef42ea9f..4545172a 100644
--- a/src/codec/ecu_fr.c
+++ b/src/codec/ecu_fr.c
@@ -164,3 +164,54 @@ int osmo_ecu_fr_conceal(struct osmo_ecu_fr_state *state, uint8_t *frame)
return 0;
}
+
+/***********************************************************************
+ * Integration with ECU core
+ ***********************************************************************/
+
+static struct osmo_ecu_state *ecu_fr_init(void *ctx, enum osmo_ecu_codec codec)
+{
+ struct osmo_ecu_state *st;
+ size_t size = sizeof(*st) + sizeof(struct osmo_ecu_fr_state);
+
+ st = talloc_named_const(ctx, size, "ecu_state_FR");
+ if (!st)
+ return NULL;
+
+ memset(st, 0, size);
+ st->codec = codec;
+
+ return st;
+}
+
+static int ecu_fr_frame_in(struct osmo_ecu_state *st, bool bfi, const uint8_t *frame,
+ unsigned int frame_bytes)
+{
+ struct osmo_ecu_fr_state *fr = (struct osmo_ecu_fr_state *) &st->data;
+ if (bfi)
+ return 0;
+
+ osmo_ecu_fr_reset(fr, frame);
+ return 0;
+}
+
+static int ecu_fr_frame_out(struct osmo_ecu_state *st, uint8_t *frame_out)
+{
+ struct osmo_ecu_fr_state *fr = (struct osmo_ecu_fr_state *) &st->data;
+
+ if (osmo_ecu_fr_conceal(fr, frame_out) == 0)
+ return GSM_FR_BYTES;
+ else
+ return -1;
+}
+
+static const struct osmo_ecu_ops osmo_ecu_ops_fr = {
+ .init = ecu_fr_init,
+ .frame_in = ecu_fr_frame_in,
+ .frame_out = ecu_fr_frame_out,
+};
+
+static __attribute__((constructor)) void on_dso_load_ecu_fr(void)
+{
+ osmo_ecu_register(&osmo_ecu_ops_fr, OSMO_ECU_CODEC_FR);
+}
diff --git a/src/context.c b/src/context.c
new file mode 100644
index 00000000..bad012bd
--- /dev/null
+++ b/src/context.c
@@ -0,0 +1,52 @@
+/*! \file context.c
+ * talloc context handling.
+ *
+ * (C) 2019 by Harald Welte <laforge@gnumonks.org>
+ * All Rights Reserverd.
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ */
+#include <string.h>
+#include <errno.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
+
+__thread struct osmo_talloc_contexts *osmo_ctx;
+
+int osmo_ctx_init(const char *id)
+{
+ osmo_ctx = talloc_named(NULL, sizeof(*osmo_ctx), "global-%s", id);
+ if (!osmo_ctx)
+ return -ENOMEM;
+ memset(osmo_ctx, 0, sizeof(*osmo_ctx));
+ osmo_ctx->global = osmo_ctx;
+ osmo_ctx->select = talloc_named_const(osmo_ctx->global, 0, "select");
+ if (!osmo_ctx->select) {
+ talloc_free(osmo_ctx);
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/* initialize osmo_ctx on main tread */
+static __attribute__((constructor)) void on_dso_load_ctx(void)
+{
+ OSMO_ASSERT(osmo_ctx_init("main") == 0);
+}
+
+/*! @} */
diff --git a/src/fsm.c b/src/fsm.c
index c8863513..1e8909ec 100644
--- a/src/fsm.c
+++ b/src/fsm.c
@@ -102,8 +102,20 @@ static __thread struct {
unsigned int depth;
/*! Talloc context to collect all deferred deallocations (FSM instances, and talloc objects if any). */
void *collect_ctx;
+ /*! See osmo_fsm_set_dealloc_ctx() */
+ void *fsm_dealloc_ctx;
} fsm_term_safely;
+/*! Internal call to free an FSM instance, which redirects to the context set by osmo_fsm_set_dealloc_ctx() if any.
+ */
+static void fsm_free_or_steal(void *talloc_object)
+{
+ if (fsm_term_safely.fsm_dealloc_ctx)
+ talloc_steal(fsm_term_safely.fsm_dealloc_ctx, talloc_object);
+ else
+ talloc_free(talloc_object);
+}
+
/*! specify if FSM instance addresses should be logged or not
*
* By default, the FSM name includes the pointer address of the \ref
@@ -139,11 +151,9 @@ void osmo_fsm_log_timeouts(bool log_timeouts)
/*! Enable safer way to deallocate cascades of terminating FSM instances.
*
- * For legacy compatibility, this is disabled by default. In newer programs / releases, it is recommended to enable this
- * feature during main() startup, since it greatly simplifies deallocating child, parent and other FSM instances without
- * running into double-free or use-after-free scenarios. When enabled, this feature changes the order of logging, which
- * may break legacy unit test expectations, and changes the order of deallocation to after the parent term event is
- * dispatched.
+ * Note, using osmo_fsm_set_dealloc_ctx() is a more general solution to this same problem.
+ * Particularly, in a program using osmo_select_main_ctx(), the simplest solution to avoid most use-after-free problems
+ * from FSM instance deallocation is using osmo_fsm_set_dealloc_ctx(OTC_SELECT).
*
* When enabled, an FSM instance termination detects whether another FSM instance is already terminating, and instead of
* deallocating immediately, collects all terminating FSM instances in a talloc context, to be bulk deallocated once all
@@ -155,6 +165,9 @@ void osmo_fsm_log_timeouts(bool log_timeouts)
*
* For illustration, see fsm_dealloc_test.c.
*
+ * When enabled, this feature changes the order of logging, which may break legacy unit test expectations, and changes
+ * the order of deallocation to after the parent term event is dispatched.
+ *
* \param[in] term_safely Pass true to switch to safer FSM instance termination behavior.
*/
void osmo_fsm_term_safely(bool term_safely)
@@ -162,6 +175,31 @@ void osmo_fsm_term_safely(bool term_safely)
fsm_term_safely_enabled = term_safely;
}
+/*! Instead of deallocating FSM instances, move them to the given talloc context.
+ *
+ * It is the caller's responsibility to clear this context to actually free the memory of terminated FSM instances.
+ * Make sure to not talloc_free(ctx) itself before setting a different osmo_fsm_set_dealloc_ctx(). To clear a ctx
+ * without the need to call osmo_fsm_set_dealloc_ctx() again, rather use talloc_free_children(ctx).
+ *
+ * For example, to defer deallocation to the next osmo_select_main_ctx() iteration, set this to OTC_SELECT.
+ *
+ * Deferring deallocation is the simplest solution to avoid most use-after-free problems from FSM instance deallocation.
+ * This is a simpler and more general solution than osmo_fsm_term_safely().
+ *
+ * To disable the feature again, pass NULL as ctx.
+ *
+ * Both osmo_fsm_term_safely() and osmo_fsm_set_dealloc_ctx() can be enabled at the same time, which will result in
+ * first collecting deallocated FSM instances in fsm_term_safely.collect_ctx, and finally reparenting that to the ctx
+ * passed here. However, in practice, it does not really make sense to enable both at the same time.
+ *
+ * \param ctx[in] Instead of talloc_free()int, talloc_steal() all future deallocated osmo_fsm_inst instances to this
+ * ctx. If NULL, go back to talloc_free() as usual.
+ */
+void osmo_fsm_set_dealloc_ctx(void *ctx)
+{
+ fsm_term_safely.fsm_dealloc_ctx = ctx;
+}
+
/*! talloc_free() the given object immediately, or once ongoing FSM terminations are done.
*
* If an FSM deallocation cascade is ongoing, talloc_steal() the given talloc_object into the talloc context that is
@@ -185,7 +223,7 @@ void osmo_fsm_term_safely(bool term_safely)
static void osmo_fsm_defer_free(void *talloc_object)
{
if (!fsm_term_safely.depth) {
- talloc_free(talloc_object);
+ fsm_free_or_steal(talloc_object);
return;
}
@@ -412,7 +450,7 @@ struct osmo_fsm_inst *osmo_fsm_inst_alloc(struct osmo_fsm *fsm, void *ctx, void
osmo_timer_setup(&fi->timer, fsm_tmr_cb, fi);
if (osmo_fsm_inst_update_id(fi, id) < 0) {
- talloc_free(fi);
+ fsm_free_or_steal(fi);
return NULL;
}
@@ -529,11 +567,11 @@ void osmo_fsm_inst_free(struct osmo_fsm_inst *fi)
* deallocate separately to avoid use-after-free errors, put it in there and deallocate all at once. */
LOGPFSM(fi, "Deallocated, including all deferred deallocations\n");
osmo_fsm_defer_free(fi);
- talloc_free(fsm_term_safely.collect_ctx);
+ fsm_free_or_steal(fsm_term_safely.collect_ctx);
fsm_term_safely.collect_ctx = NULL;
} else {
LOGPFSM(fi, "Deallocated\n");
- talloc_free(fi);
+ fsm_free_or_steal(fi);
}
fsm_term_safely.root_fi = NULL;
}
@@ -592,6 +630,13 @@ static int state_chg(struct osmo_fsm_inst *fi, uint32_t new_state,
const struct osmo_fsm_state *st = &fsm->states[fi->state];
struct timeval remaining;
+ if (fi->proc.terminating) {
+ LOGPFSMSRC(fi, file, line,
+ "FSM instance already terminating, not changing state to %s\n",
+ osmo_fsm_state_name(fsm, new_state));
+ return -EINVAL;
+ }
+
/* validate if new_state is a valid state */
if (!(st->out_state_mask & (1 << new_state))) {
LOGPFSMLSRC(fi, LOGL_ERROR, file, line,
@@ -802,6 +847,14 @@ int _osmo_fsm_inst_dispatch(struct osmo_fsm_inst *fi, uint32_t event, void *data
}
fsm = fi->fsm;
+
+ if (fi->proc.terminating) {
+ LOGPFSMSRC(fi, file, line,
+ "FSM instance already terminating, not dispatching event %s\n",
+ osmo_fsm_event_name(fsm, event));
+ return -EINVAL;
+ }
+
OSMO_ASSERT(fi->state < fsm->num_states);
fs = &fi->fsm->states[fi->state];
diff --git a/src/gb/gprs_bssgp.c b/src/gb/gprs_bssgp.c
index b695c284..896f1c5a 100644
--- a/src/gb/gprs_bssgp.c
+++ b/src/gb/gprs_bssgp.c
@@ -141,6 +141,15 @@ struct bssgp_bvc_ctx *btsctx_alloc(uint16_t bvci, uint16_t nsei)
return ctx;
}
+void bssgp_bvc_ctx_free(struct bssgp_bvc_ctx *ctx)
+{
+ if (!ctx)
+ return;
+ rate_ctr_group_free(ctx->ctrg);
+ llist_del(&ctx->list);
+ talloc_free(ctx);
+}
+
/* Chapter 10.4.5: Flow Control BVC ACK */
static int bssgp_tx_fc_bvc_ack(uint16_t nsei, uint8_t tag, uint16_t ns_bvci)
{
diff --git a/src/gb/gprs_bssgp_vty.c b/src/gb/gprs_bssgp_vty.c
index 3af6517f..5dab94e7 100644
--- a/src/gb/gprs_bssgp_vty.c
+++ b/src/gb/gprs_bssgp_vty.c
@@ -181,21 +181,27 @@ DEFUN(logging_fltr_bvc,
"BVCI of the BVC to be filtered\n"
"BSSGP Virtual Connection Identifier (BVCI)\n")
{
- struct log_target *tgt = osmo_log_vty2tgt(vty);
+ struct log_target *tgt;
struct bssgp_bvc_ctx *bvc;
uint16_t nsei = atoi(argv[0]);
uint16_t bvci = atoi(argv[1]);
- if (!tgt)
+ log_tgt_mutex_lock();
+ tgt = osmo_log_vty2tgt(vty);
+ if (!tgt) {
+ log_tgt_mutex_unlock();
return CMD_WARNING;
+ }
bvc = btsctx_by_bvci_nsei(bvci, nsei);
if (!bvc) {
vty_out(vty, "No BVC by that identifier%s", VTY_NEWLINE);
+ log_tgt_mutex_unlock();
return CMD_WARNING;
}
log_set_bvc_filter(tgt, bvc);
+ log_tgt_mutex_unlock();
return CMD_SUCCESS;
}
diff --git a/src/gb/gprs_ns_vty.c b/src/gb/gprs_ns_vty.c
index 53c71a9a..9cffb71d 100644
--- a/src/gb/gprs_ns_vty.c
+++ b/src/gb/gprs_ns_vty.c
@@ -508,10 +508,6 @@ DEFUN(cfg_frgre_local_ip, cfg_frgre_local_ip_cmd,
{
struct in_addr ia;
- if (!vty_nsi->frgre.enabled) {
- vty_out(vty, "FR/GRE is not enabled%s", VTY_NEWLINE);
- return CMD_WARNING;
- }
inet_aton(argv[0], &ia);
vty_nsi->frgre.local_ip = osmo_ntohl(ia.s_addr);
@@ -587,12 +583,16 @@ DEFUN(logging_fltr_nsvc,
"Identify NS-VC by NSVCI\n"
"Numeric identifier\n")
{
- struct log_target *tgt = osmo_log_vty2tgt(vty);
+ struct log_target *tgt;
struct gprs_nsvc *nsvc;
uint16_t id = atoi(argv[1]);
- if (!tgt)
+ log_tgt_mutex_lock();
+ tgt = osmo_log_vty2tgt(vty);
+ if (!tgt) {
+ log_tgt_mutex_unlock();
return CMD_WARNING;
+ }
if (!strcmp(argv[0], "nsei"))
nsvc = gprs_nsvc_by_nsei(vty_nsi, id);
@@ -601,10 +601,12 @@ DEFUN(logging_fltr_nsvc,
if (!nsvc) {
vty_out(vty, "No NS-VC by that identifier%s", VTY_NEWLINE);
+ log_tgt_mutex_unlock();
return CMD_WARNING;
}
log_set_nsvc_filter(tgt, nsvc);
+ log_tgt_mutex_unlock();
return CMD_SUCCESS;
}
diff --git a/src/gb/libosmogb.map b/src/gb/libosmogb.map
index 5e675a66..ad139c1c 100644
--- a/src/gb/libosmogb.map
+++ b/src/gb/libosmogb.map
@@ -80,6 +80,7 @@ gprs_nsvc_state_append;
gprs_log_filter_fn;
btsctx_alloc;
+bssgp_bvc_ctx_free;
btsctx_by_bvci_nsei;
btsctx_by_raid_cid;
diff --git a/src/gsm/bts_features.c b/src/gsm/bts_features.c
index 4ec24fa2..e4ff76c8 100644
--- a/src/gsm/bts_features.c
+++ b/src/gsm/bts_features.c
@@ -38,6 +38,7 @@ const struct value_string osmo_bts_features_descs[] = {
{ BTS_FEAT_SPEECH_F_EFR, "Fullrate speech EFR" },
{ BTS_FEAT_SPEECH_F_AMR, "Fullrate speech AMR" },
{ BTS_FEAT_SPEECH_H_AMR, "Halfrate speech AMR" },
+ { BTS_FEAT_ETWS_PN, "ETWS Primary Notification via PCH" },
{ 0, NULL }
};
diff --git a/src/gsm/cbsp.c b/src/gsm/cbsp.c
index 84b92358..ccc2df53 100644
--- a/src/gsm/cbsp.c
+++ b/src/gsm/cbsp.c
@@ -515,7 +515,7 @@ static int cbsp_decode_cell_list(struct osmo_cbsp_cell_list *cl, void *ctx,
osmo_cbsp_errstr = "cell list: error decoding cell_id_union";
return rc;
}
- cur += rc;
+ cur += gsm0808_cell_id_size(cl->id_discr);
llist_add_tail(&ent->list, &cl->list);
}
return 0;
@@ -538,7 +538,7 @@ static int cbsp_decode_fail_list(struct llist_head *fl, void *ctx,
osmo_cbsp_errstr = "fail list: error decoding cell_id_union";
return rc;
}
- cur += rc;
+ cur += gsm0808_cell_id_size(ent->id_discr);
ent->cause = *cur++;
llist_add_tail(&ent->list, fl);
}
@@ -562,7 +562,7 @@ static int cbsp_decode_loading_list(struct osmo_cbsp_loading_list *ll, void *ctx
osmo_cbsp_errstr = "load list: error decoding cell_id_union";
return rc;
}
- cur += rc;
+ cur += gsm0808_cell_id_size(ll->id_discr);
if (cur + 2 > buf + len) {
talloc_free(ent);
osmo_cbsp_errstr = "load list: truncated IE";
@@ -592,7 +592,7 @@ static int cbsp_decode_num_compl_list(struct osmo_cbsp_num_compl_list *cl, void
osmo_cbsp_errstr = "completed list: error decoding cell_id_union";
return rc;
}
- cur += rc;
+ cur += gsm0808_cell_id_size(cl->id_discr);
if (cur + 3 > buf + len) {
talloc_free(ent);
osmo_cbsp_errstr = "completed list: truncated IE";
@@ -687,8 +687,8 @@ static int cbsp_dec_write_repl(struct osmo_cbsp_write_replace *out, const struct
}
page = talloc_zero(ctx, struct osmo_cbsp_content);
OSMO_ASSERT(page);
- page->user_len = *(ie-1); /* length byte before payload */
- memcpy(page->data, ie, sizeof(page->data));
+ page->user_len = ie[0]; /* length byte before payload */
+ memcpy(page->data, ie+1, sizeof(page->data));
llist_add_tail(&page->list, &out->u.cbs.msg_content);
}
} else {
@@ -1468,7 +1468,6 @@ int osmo_cbsp_recv_buffered(void *ctx, int fd, struct msgb **rmsg, struct msgb *
return rc;
discard_msg:
- printf("discard_msg\n");
if (tmp_msg)
*tmp_msg = NULL;
msgb_free(msg);
diff --git a/src/gsm/gsm0502.c b/src/gsm/gsm0502.c
index 53259a42..1a71e617 100644
--- a/src/gsm/gsm0502.c
+++ b/src/gsm/gsm0502.c
@@ -27,6 +27,9 @@
#include <osmocom/gsm/gsm0502.h>
#include <osmocom/gsm/gsm48.h>
#include <osmocom/gsm/rsl.h>
+#include <osmocom/gsm/gsm_utils.h>
+#include <osmocom/core/logging.h>
+#include <inttypes.h>
unsigned int
gsm0502_calc_paging_group(struct gsm48_control_channel_descr *chan_desc, uint64_t imsi)
@@ -44,3 +47,157 @@ gsm0502_calc_paging_group(struct gsm48_control_channel_descr *chan_desc, uint64_
return group;
}
+
+/* Clause 7 Table 1 of 5 Mapping of logical channels onto physical channels */
+#define TCH_REPEAT_LENGTH 13
+#define FACCH_F_REPEAT_LENGTH 13
+#define FACCH_H_REPEAT_LENGTH 26
+
+static const uint8_t gsm0502_tch_f_traffic_block_map[3][8] = {
+ {0, 1, 2, 3, 4, 5, 6, 7},
+ {4, 5, 6, 7, 8, 9, 10, 11},
+ {8, 9, 10, 11, 0, 1, 2, 3}
+};
+
+static const uint8_t gsm0502_tch_h0_traffic_block_map[3][4] = {
+ {0, 2, 4, 6},
+ {4, 6, 8, 10},
+ {8, 10, 0, 2}
+};
+
+static const uint8_t gsm0502_tch_h1_traffic_block_map[3][4] = {
+ {1, 3, 5, 7},
+ {5, 7, 9, 11},
+ {9, 11, 1, 3}
+};
+
+static const uint8_t gsm0502_tch_f_facch_block_map[3][8] = {
+ {0, 1, 2, 3, 4, 5, 6, 7},
+ {4, 5, 6, 7, 8, 9, 10, 11},
+ {8, 9, 10, 11, 0, 1, 2, 3}
+};
+
+static const uint8_t gsm0502_tch_h0_facch_block_map[3][6] = {
+ {0, 2, 4, 6, 8, 10},
+ {8, 10, 13, 15, 17, 19},
+ {17, 19, 21, 23, 0, 2}
+};
+
+static const uint8_t gsm0502_tch_h1_facch_block_map[3][6] = {
+ {1, 3, 5, 7, 9, 11},
+ {9, 11, 14, 16, 18, 20},
+ {18, 20, 22, 24, 1, 3}
+};
+
+/* Struct to describe a remapping function for block frame nbumbers. The member
+ * blockend describes the ending of a block for which we want to determine the
+ * beginning frame number. The member distance describes the value we need to
+ * subtract from the blockend frame number in order to get the beginning of the
+ * the block. The member cycle describes the Repeat length in TDMA frames we
+ * are dealing with. For traffic channels this is always 13, for control
+ * channels it is different. The member len simply defines amount of
+ * blockendings and distances we store in the remap table */
+struct fn_remap_table {
+ unsigned int cycle;
+ unsigned int len;
+ uint8_t blockend[8];
+ uint8_t distance[8];
+};
+
+/* Memory to hold the remap tables we will automatically generate on startup */
+static struct fn_remap_table tch_f_remap_table;
+static struct fn_remap_table tch_h0_remap_table;
+static struct fn_remap_table tch_h1_remap_table;
+static struct fn_remap_table facch_f_remap_table;
+static struct fn_remap_table facch_h0_remap_table;
+static struct fn_remap_table facch_h1_remap_table;
+static struct fn_remap_table *fn_remap_table_ptr[FN_REMAP_MAX];
+
+/* Generate a remap table from a given block map. A block map lists the block
+ * layout as defined in GSM 05.02, Clause 7 Table 1 of 5, one block per row.
+ * Parameters:
+ * table: name of the remap table to output
+ * map: traffic block map input
+ * rows: length of the traffic block map
+ * cols: witdh of the traffic block map
+ * repeat: repeat length in TDMA frames (cycle) */
+#define fn_remap_table_from_traffic_block_map(table, map, rows, cols, repeat) \
+ for(i=0;i<rows;i++) { \
+ table.blockend[i] = map[i][cols-1]; \
+ if(map[i][0] <= map[i][cols-1]) \
+ table.distance[i] = map[i][cols-1] - map[i][0]; \
+ else \
+ table.distance[i] = repeat - map[i][0] + map[i][cols-1]; \
+ } \
+ table.cycle = repeat; \
+ table.len = rows;
+
+/* Automatically generate fn remap tables on startupmake */
+static __attribute__ ((constructor))
+void fn_remap_tables_build(void)
+{
+ /* Required by macro */
+ unsigned int i;
+
+ /* Generate tables */
+ fn_remap_table_from_traffic_block_map(tch_f_remap_table,
+ gsm0502_tch_f_traffic_block_map, 3, 8,
+ TCH_REPEAT_LENGTH);
+ fn_remap_table_from_traffic_block_map(tch_h0_remap_table,
+ gsm0502_tch_h0_traffic_block_map, 3, 4,
+ TCH_REPEAT_LENGTH);
+ fn_remap_table_from_traffic_block_map(tch_h1_remap_table,
+ gsm0502_tch_h1_traffic_block_map, 3, 4,
+ TCH_REPEAT_LENGTH);
+ fn_remap_table_from_traffic_block_map(facch_f_remap_table,
+ gsm0502_tch_f_facch_block_map, 3, 8,
+ FACCH_F_REPEAT_LENGTH);
+ fn_remap_table_from_traffic_block_map(facch_h0_remap_table,
+ gsm0502_tch_h0_facch_block_map, 3, 6,
+ FACCH_H_REPEAT_LENGTH);
+ fn_remap_table_from_traffic_block_map(facch_h1_remap_table,
+ gsm0502_tch_h1_facch_block_map, 3, 6,
+ FACCH_H_REPEAT_LENGTH);
+
+ fn_remap_table_ptr[FN_REMAP_TCH_F] = &tch_f_remap_table;
+ fn_remap_table_ptr[FN_REMAP_TCH_H0] = &tch_h0_remap_table;
+ fn_remap_table_ptr[FN_REMAP_TCH_H1] = &tch_h1_remap_table;
+ fn_remap_table_ptr[FN_REMAP_FACCH_F] = &facch_f_remap_table;
+ fn_remap_table_ptr[FN_REMAP_FACCH_H0] = &facch_h0_remap_table;
+ fn_remap_table_ptr[FN_REMAP_FACCH_H1] = &facch_h1_remap_table;
+}
+
+/*! Calculate the frame number of the beginning of a block.
+ * \param[in] fn frame number of the block ending.
+ * \param[in] channel channel type (see also enum fn_remap_channel).
+ * \returns frame number of the beginning of the block or input frame number if
+ * remapping was not possible. */
+uint32_t gsm0502_fn_remap(uint32_t fn, enum gsm0502_fn_remap_channel channel)
+{
+ uint8_t fn_cycle;
+ uint8_t i;
+ int sub = -1;
+ uint32_t fn_map;
+ struct fn_remap_table *table;
+
+ OSMO_ASSERT(channel < ARRAY_SIZE(fn_remap_table_ptr));
+ table = fn_remap_table_ptr[(uint8_t)channel];
+
+ fn_cycle = fn % table->cycle;
+
+ for (i = 0; i < table->len; i++) {
+ if (table->blockend[i] == fn_cycle) {
+ sub = table->distance[i];
+ break;
+ }
+ }
+
+ if (sub == -1) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "could not remap frame number!, fn=%"PRIu32"\n", fn);
+ return fn;
+ }
+
+ fn_map = (fn + GSM_MAX_FN - sub) % GSM_MAX_FN;
+
+ return fn_map;
+}
diff --git a/src/gsm/gsm0808_utils.c b/src/gsm/gsm0808_utils.c
index 364a04fe..7416d8f5 100644
--- a/src/gsm/gsm0808_utils.c
+++ b/src/gsm/gsm0808_utils.c
@@ -767,6 +767,27 @@ int gsm0808_dec_encrypt_info(struct gsm0808_encrypt_info *ei,
return (int)(elem - old_elem);
}
+/* Return the size of the value part of a cell identifier of given type */
+int gsm0808_cell_id_size(enum CELL_IDENT discr)
+{
+ switch (discr) {
+ case CELL_IDENT_WHOLE_GLOBAL:
+ return 7;
+ case CELL_IDENT_LAC_AND_CI:
+ return 4;
+ case CELL_IDENT_CI:
+ return 2;
+ case CELL_IDENT_LAI_AND_LAC:
+ return 5;
+ case CELL_IDENT_LAC:
+ return 2;
+ case CELL_IDENT_BSS:
+ case CELL_IDENT_NO_CELL:
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
/*! Decode a single GSM 08.08 Cell ID list element payload
* \param[out] out caller-provided output union
* \param[in] discr Cell ID discriminator describing type to be decoded
diff --git a/src/gsm/gsm48.c b/src/gsm/gsm48.c
index 2d6deb56..17b0829d 100644
--- a/src/gsm/gsm48.c
+++ b/src/gsm/gsm48.c
@@ -1390,4 +1390,25 @@ int osmo_gsm48_classmark_supports_a5(const struct osmo_gsm48_classmark *cm, uint
}
}
+/*! Decode power class from Classmark1/2 RF power capability field.
+ * \param[in] rf_power_cap The RF power capability field (3 bits).
+ * \param[in] band the band of the arfcn from where the classmark was received
+ * \return the MS power class on success, negative on error.
+ */
+int8_t osmo_gsm48_rfpowercap2powerclass(enum gsm_band band, uint8_t rf_power_cap)
+{
+ switch (band) {
+ case GSM_BAND_1800:
+ case GSM_BAND_1900:
+ if (rf_power_cap > 2)
+ return -1;
+ return rf_power_cap + 1;
+ default:
+ if (rf_power_cap > 4)
+ return -1;
+ return rf_power_cap + 1;
+ }
+}
+
+
/*! @} */
diff --git a/src/gsm/gsm_utils.c b/src/gsm/gsm_utils.c
index 365920fa..ae77a9dc 100644
--- a/src/gsm/gsm_utils.c
+++ b/src/gsm/gsm_utils.c
@@ -501,8 +501,8 @@ size_t gsm0858_rsl_ul_meas_enc(struct gsm_meas_rep_unidir *mru, bool dtxd_used,
/*! Convert power class to dBm according to GSM TS 05.05
* \param[in] band GSM frequency band
* \param[in] class GSM power class
- * \returns maximum transmit power of power class in dBm */
-unsigned int ms_class_gmsk_dbm(enum gsm_band band, int class)
+ * \returns maximum transmit power of power class in dBm, negative on error */
+int ms_class_gmsk_dbm(enum gsm_band band, int class)
{
switch (band) {
case GSM_BAND_450:
diff --git a/src/gsm/gsup.c b/src/gsm/gsup.c
index 2e6690ee..2f9d85d8 100644
--- a/src/gsm/gsup.c
+++ b/src/gsm/gsup.c
@@ -297,6 +297,7 @@ int osmo_gsup_decode(const uint8_t *const_data, size_t data_len,
struct osmo_gsup_message *gsup_msg)
{
int rc;
+ int i;
uint8_t tag;
/* the shift/match functions expect non-const pointers, but we'll
* either copy the data or cast pointers back to const before returning
@@ -459,6 +460,21 @@ int osmo_gsup_decode(const uint8_t *const_data, size_t data_len,
gsup_msg->cn_domain = *value;
break;
+ case OSMO_GSUP_SUPPORTED_RAT_TYPES_IE:
+ if (value_len > ARRAY_SIZE(gsup_msg->supported_rat_types)) {
+ LOGP(DLGSUP, LOGL_ERROR, "nr of supported RAT types %zu > %zu\n",
+ value_len, ARRAY_SIZE(gsup_msg->supported_rat_types));
+ return -GMM_CAUSE_COND_IE_ERR;
+ }
+ for (i = 0; i < value_len; i++)
+ gsup_msg->supported_rat_types[i] = value[i];
+ gsup_msg->supported_rat_types_len = value_len;
+ break;
+
+ case OSMO_GSUP_CURRENT_RAT_TYPE_IE:
+ gsup_msg->current_rat_type = *value;
+ break;
+
case OSMO_GSUP_CHARG_CHAR_IE:
gsup_msg->pdp_charg_enc = value;
gsup_msg->pdp_charg_enc_len = value_len;
@@ -856,6 +872,25 @@ int osmo_gsup_encode(struct msgb *msg, const struct osmo_gsup_message *gsup_msg)
if ((u8 = gsup_msg->cause_sm))
msgb_tlv_put(msg, OSMO_GSUP_CAUSE_SM_IE, sizeof(u8), &u8);
+ if (gsup_msg->supported_rat_types_len) {
+ int i;
+ uint8_t *len = msgb_tl_put(msg, OSMO_GSUP_SUPPORTED_RAT_TYPES_IE);
+ *len = gsup_msg->supported_rat_types_len;
+ for (i = 0; i < gsup_msg->supported_rat_types_len; i++) {
+ if (!gsup_msg->supported_rat_types[i] ||
+ gsup_msg->supported_rat_types[i] >= OSMO_RAT_COUNT) {
+ LOGP(DLGSUP, LOGL_ERROR, "Failed to encode RAT type %s (nr %d)\n",
+ osmo_rat_type_name(gsup_msg->supported_rat_types[i]), i);
+ return -EINVAL;
+ }
+ msgb_v_put(msg, gsup_msg->supported_rat_types[i]);
+ }
+ }
+ if (gsup_msg->current_rat_type != OSMO_RAT_UNKNOWN) {
+ u8 = gsup_msg->current_rat_type;
+ msgb_tlv_put(msg, OSMO_GSUP_CURRENT_RAT_TYPE_IE, sizeof(u8), &u8);
+ }
+
return 0;
}
diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map
index ea1f759d..a0e3b324 100644
--- a/src/gsm/libosmogsm.map
+++ b/src/gsm/libosmogsm.map
@@ -107,6 +107,7 @@ gsm0480_gen_return_error;
gsm0480_gen_reject;
gsm0502_calc_paging_group;
+gsm0502_fn_remap;
gsm0503_xcch;
gsm0503_rach;
@@ -645,6 +646,7 @@ osmo_gsm48_classmark_a5_name;
osmo_gsm48_classmark_a5_name_buf;
osmo_gsm48_classmark_a5_name_c;
osmo_gsm48_classmark_update;
+osmo_gsm48_rfpowercap2powerclass;
cbsp_msg_type_names;
cbsp_iei_names;
diff --git a/src/gsm/rsl.c b/src/gsm/rsl.c
index 3be01112..5534aa2a 100644
--- a/src/gsm/rsl.c
+++ b/src/gsm/rsl.c
@@ -389,6 +389,7 @@ static const struct value_string rsl_msgt_names[] = {
{ RSL_MT_TFO_REP, "TFO_REP" },
{ RSL_MT_TFO_MOD_REQ, "TFO_MOD_REQ" },
{ RSL_MT_LOCATION_INFO, "LOCATION_INFO" },
+ { RSL_MT_OSMO_ETWS_CMD, "OSMO_ETWS_CMD" },
{ 0, NULL }
};
diff --git a/src/logging.c b/src/logging.c
index 1c3544fa..b030f8a6 100644
--- a/src/logging.c
+++ b/src/logging.c
@@ -42,6 +42,7 @@
#include <time.h>
#include <sys/time.h>
#include <errno.h>
+#include <pthread.h>
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
@@ -63,6 +64,56 @@ static struct log_context log_context;
void *tall_log_ctx = NULL;
LLIST_HEAD(osmo_log_target_list);
+#if (!EMBEDDED)
+/*! This mutex must be held while using osmo_log_target_list or any of its
+ log_targets in a multithread program. Prevents race conditions between threads
+ like producing unordered timestamps or VTY deleting a target while another
+ thread is writing to it */
+static pthread_mutex_t osmo_log_tgt_mutex;
+static bool osmo_log_tgt_mutex_on = false;
+
+/*! Enable multithread support (mutex) in libosmocore logging system.
+ * Must be called by processes willing to use logging subsystem from several
+ * threads. Once enabled, it's not possible to disable it again.
+ */
+void log_enable_multithread(void) {
+ if (osmo_log_tgt_mutex_on)
+ return;
+ pthread_mutex_init(&osmo_log_tgt_mutex, NULL);
+ osmo_log_tgt_mutex_on = true;
+}
+
+/*! Acquire the osmo_log_tgt_mutex. Don't use this function directly, always use
+ * macro log_tgt_mutex_lock() instead.
+ */
+void log_tgt_mutex_lock_impl(void) {
+ /* These lines are useful to debug scenarios where there's only 1 thread
+ and a double lock appears, for instance during startup and some
+ unlock() missing somewhere:
+ if (osmo_log_tgt_mutex_on && pthread_mutex_trylock(&osmo_log_tgt_mutex) != 0)
+ osmo_panic("acquiring already locked mutex!\n");
+ return;
+ */
+
+ if (osmo_log_tgt_mutex_on)
+ pthread_mutex_lock(&osmo_log_tgt_mutex);
+}
+
+/*! Release the osmo_log_tgt_mutex. Don't use this function directly, always use
+ * macro log_tgt_mutex_unlock() instead.
+ */
+void log_tgt_mutex_unlock_impl(void) {
+ if (osmo_log_tgt_mutex_on)
+ pthread_mutex_unlock(&osmo_log_tgt_mutex);
+}
+
+#else /* if (!EMBEDDED) */
+#pragma message ("logging multithread support disabled in embedded build")
+void log_enable_multithread(void) {}
+void log_tgt_mutex_lock_impl(void) {}
+void log_tgt_mutex_unlock_impl(void) {}
+#endif /* if (!EMBEDDED) */
+
const struct value_string loglevel_strs[] = {
{ LOGL_DEBUG, "DEBUG" },
{ LOGL_INFO, "INFO" },
@@ -532,6 +583,8 @@ void osmo_vlogp(int subsys, int level, const char *file, int line,
subsys = map_subsys(subsys);
+ log_tgt_mutex_lock();
+
llist_for_each_entry(tar, &osmo_log_target_list, entry) {
va_list bp;
@@ -548,6 +601,8 @@ void osmo_vlogp(int subsys, int level, const char *file, int line,
_output(tar, subsys, level, file, line, cont, format, bp);
va_end(bp);
}
+
+ log_tgt_mutex_unlock();
}
/*! logging function used by DEBUGP() macro
@@ -870,6 +925,7 @@ struct log_target *log_target_create_file(const char *fname)
* \param[in] type Log target type
* \param[in] fname File name
* \returns Log target (if found), NULL otherwise
+ * Must be called with mutex osmo_log_tgt_mutex held, see log_tgt_mutex_lock.
*/
struct log_target *log_target_find(int type, const char *fname)
{
@@ -942,6 +998,8 @@ int log_targets_reopen(void)
struct log_target *tar;
int rc = 0;
+ log_tgt_mutex_lock();
+
llist_for_each_entry(tar, &osmo_log_target_list, entry) {
switch (tar->type) {
case LOG_TGT_TYPE_FILE:
@@ -953,6 +1011,8 @@ int log_targets_reopen(void)
}
}
+ log_tgt_mutex_unlock();
+
return rc;
}
@@ -1015,6 +1075,8 @@ void log_fini(void)
{
struct log_target *tar, *tar2;
+ log_tgt_mutex_lock();
+
llist_for_each_entry_safe(tar, tar2, &osmo_log_target_list, entry)
log_target_destroy(tar);
@@ -1022,6 +1084,8 @@ void log_fini(void)
osmo_log_info = NULL;
talloc_free(tall_log_ctx);
tall_log_ctx = NULL;
+
+ log_tgt_mutex_unlock();
}
/*! Check whether a log entry will be generated.
@@ -1036,15 +1100,19 @@ int log_check_level(int subsys, unsigned int level)
/* TODO: The following could/should be cached (update on config) */
+ log_tgt_mutex_lock();
+
llist_for_each_entry(tar, &osmo_log_target_list, entry) {
if (!should_log_to_target(tar, subsys, level))
continue;
/* This might get logged (ignoring filters) */
+ log_tgt_mutex_unlock();
return 1;
}
/* We are sure, that this will not be logged. */
+ log_tgt_mutex_unlock();
return 0;
}
diff --git a/src/select.c b/src/select.c
index 7ce135f5..b997122e 100644
--- a/src/select.c
+++ b/src/select.c
@@ -36,6 +36,8 @@
#include <osmocom/core/linuxlist.h>
#include <osmocom/core/timer.h>
#include <osmocom/core/logging.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/utils.h>
#include "../config.h"
@@ -48,9 +50,11 @@
*
* \file select.c */
-static int maxfd = 0;
-static LLIST_HEAD(osmo_fds);
-static int unregistered_count;
+/* keep a set of file descriptors per-thread, so that each thread can have its own
+ * distinct set of file descriptors to interact with */
+static __thread int maxfd = 0;
+static __thread struct llist_head osmo_fds; /* TLS cannot use LLIST_HEAD() */
+static __thread int unregistered_count;
/*! Set up an osmo-fd. Will not register it.
* \param[inout] ofd Osmo FD to be set-up
@@ -233,11 +237,7 @@ restart:
return work;
}
-/*! select main loop integration
- * \param[in] polling should we pollonly (1) or block on select (0)
- * \returns 0 if no fd handled; 1 if fd handled; negative in case of error
- */
-int osmo_select_main(int polling)
+static int _osmo_select_main(int polling)
{
fd_set readset, writeset, exceptset;
int rc;
@@ -259,10 +259,42 @@ int osmo_select_main(int polling)
/* fire timers */
osmo_timers_update();
+ OSMO_ASSERT(osmo_ctx->select);
+
/* call registered callback functions */
return osmo_fd_disp_fds(&readset, &writeset, &exceptset);
}
+/*! select main loop integration
+ * \param[in] polling should we pollonly (1) or block on select (0)
+ * \returns 0 if no fd handled; 1 if fd handled; negative in case of error
+ */
+int osmo_select_main(int polling)
+{
+ int rc = _osmo_select_main(polling);
+#ifndef EMBEDDED
+ if (talloc_total_size(osmo_ctx->select) != 0) {
+ osmo_panic("You cannot use the 'select' volatile "
+ "context if you don't use osmo_select_main_ctx()!\n");
+ }
+#endif
+ return rc;
+}
+
+#ifndef EMBEDDED
+/*! select main loop integration with temporary select-dispatch talloc context
+ * \param[in] polling should we pollonly (1) or block on select (0)
+ * \returns 0 if no fd handled; 1 if fd handled; negative in case of error
+ */
+int osmo_select_main_ctx(int polling)
+{
+ int rc = _osmo_select_main(polling);
+ /* free all the children of the volatile 'select' scope context */
+ talloc_free_children(osmo_ctx->select);
+ return rc;
+}
+#endif
+
/*! find an osmo_fd based on the integer fd
* \param[in] fd file descriptor to use as search key
* \returns \ref osmo_fd for \ref fd; NULL in case it doesn't exist */
@@ -277,6 +309,18 @@ struct osmo_fd *osmo_fd_get_by_fd(int fd)
return NULL;
}
+/*! initialize the osmocom select abstraction for the current thread */
+void osmo_select_init(void)
+{
+ INIT_LLIST_HEAD(&osmo_fds);
+}
+
+/* ensure main thread always has pre-initialized osmo_fds */
+static __attribute__((constructor)) void on_dso_load_select(void)
+{
+ osmo_select_init();
+}
+
#ifdef HAVE_SYS_TIMERFD_H
#include <sys/timerfd.h>
diff --git a/src/serial.c b/src/serial.c
index 1b5a4cfd..31cb81d1 100644
--- a/src/serial.c
+++ b/src/serial.c
@@ -97,7 +97,7 @@ osmo_serial_init(const char *dev, speed_t baudrate)
tio.c_cflag &= ~(PARENB | CSTOPB | CSIZE | CRTSCTS);
tio.c_cflag |= (CREAD | CLOCAL | CS8);
tio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
- tio.c_iflag |= (INPCK | ISTRIP);
+ tio.c_iflag |= (INPCK);
tio.c_iflag &= ~(ISTRIP | IXON | IXOFF | IGNBRK | INLCR | ICRNL | IGNCR);
tio.c_oflag &= ~(OPOST | ONLCR);
diff --git a/src/sim/class_tables.c b/src/sim/class_tables.c
index 593b2e83..6f541ee2 100644
--- a/src/sim/class_tables.c
+++ b/src/sim/class_tables.c
@@ -112,7 +112,7 @@ const struct osim_cla_ins_card_profile osim_gsm1111_cic_profile = {
/* ETSI TS 102 221, Table 10.5, CLA = 0x0x, 0x4x or 0x6x */
static const uint8_t uicc_ins_tbl_046[256] = {
- [0xA4] = 4, /* SELET FILE */
+ [0xA4] = 4, /* SELECT FILE */
[0xB0] = 2, /* READ BINARY */
[0xD6] = 3, /* UPDATE BINARY */
[0xB2] = 2, /* READ RECORD */
diff --git a/src/sockaddr_str.c b/src/sockaddr_str.c
index d683c7d2..f523050c 100644
--- a/src/sockaddr_str.c
+++ b/src/sockaddr_str.c
@@ -66,6 +66,35 @@ bool osmo_sockaddr_str_is_set(const struct osmo_sockaddr_str *sockaddr_str)
&& (sockaddr_str->af == AF_INET || sockaddr_str->af == AF_INET6);
}
+/*! Return true if IP and port are valid and nonzero.
+ * \param[in] sockaddr_str The instance to examine.
+ * \return True iff ip can be converted to a nonzero IP address, and port is not 0.
+ */
+bool osmo_sockaddr_str_is_nonzero(const struct osmo_sockaddr_str *sockaddr_str)
+{
+ uint32_t ipv4;
+ struct in6_addr ipv6_zero = {};
+ struct in6_addr ipv6;
+
+ if (!osmo_sockaddr_str_is_set(sockaddr_str))
+ return false;
+
+ switch (sockaddr_str->af) {
+ case AF_INET:
+ if (osmo_sockaddr_str_to_32(sockaddr_str, &ipv4))
+ return false;
+ return ipv4 != 0;
+
+ case AF_INET6:
+ if (osmo_sockaddr_str_to_in6_addr(sockaddr_str, &ipv6))
+ return false;
+ return memcmp(&ipv6, &ipv6_zero, sizeof(ipv6)) != 0;
+
+ default:
+ return false;
+ }
+}
+
/*! Distinguish between valid IPv4 and IPv6 strings.
* This does not verify whether the string is a valid IP address; it assumes that the input is a valid IP address, and
* on that premise returns whether it is an IPv4 or IPv6 string, by looking for '.' and ':' characters. It is safe to
diff --git a/src/socket.c b/src/socket.c
index c50af55b..9b1d30ec 100644
--- a/src/socket.c
+++ b/src/socket.c
@@ -53,10 +53,14 @@
#include <netdb.h>
#include <ifaddrs.h>
+#ifdef HAVE_LIBSCTP
+#include <netinet/sctp.h>
+#endif
+
static struct addrinfo *addrinfo_helper(uint16_t family, uint16_t type, uint8_t proto,
const char *host, uint16_t port, bool passive)
{
- struct addrinfo hints, *result;
+ struct addrinfo hints, *result, *rp;
char portbuf[6];
int rc;
@@ -66,6 +70,7 @@ static struct addrinfo *addrinfo_helper(uint16_t family, uint16_t type, uint8_t
if (type == SOCK_RAW) {
/* Workaround for glibc, that returns EAI_SERVICE (-8) if
* SOCK_RAW and IPPROTO_GRE is used.
+ * http://sourceware.org/bugzilla/show_bug.cgi?id=15015
*/
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
@@ -84,9 +89,47 @@ static struct addrinfo *addrinfo_helper(uint16_t family, uint16_t type, uint8_t
return NULL;
}
+ for (rp = result; rp != NULL; rp = rp->ai_next) {
+ /* Workaround for glibc again */
+ if (type == SOCK_RAW) {
+ rp->ai_socktype = SOCK_RAW;
+ rp->ai_protocol = proto;
+ }
+ }
+
return result;
}
+#ifdef HAVE_LIBSCTP
+/*! Retrieve an array of addrinfo with specified hints, one for each host in the hosts array.
+ * \param[out] addrinfo array of addrinfo pointers, will be filled by the function on success.
+ * Its size must be at least the one of hosts.
+ * \param[in] family Socket family like AF_INET, AF_INET6.
+ * \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM.
+ * \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP.
+ * \param[in] hosts array of char pointers (strings) containing the addresses to query.
+ * \param[in] host_cnt length of the hosts array (in items).
+ * \param[in] port port number in host byte order.
+ * \param[in] passive whether to include the AI_PASSIVE flag in getaddrinfo() hints.
+ * \returns 0 is returned on success together with a filled addrinfo array; negative on error
+ */
+static int addrinfo_helper_multi(struct addrinfo **addrinfo, uint16_t family, uint16_t type, uint8_t proto,
+ const char **hosts, size_t host_cnt, uint16_t port, bool passive)
+{
+ int i, j;
+
+ for (i = 0; i < host_cnt; i++) {
+ addrinfo[i] = addrinfo_helper(family, type, proto, hosts[i], port, passive);
+ if (!addrinfo[i]) {
+ for (j = 0; j < i; j++)
+ freeaddrinfo(addrinfo[j]);
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+#endif /* HAVE_LIBSCTP*/
+
static int socket_helper(const struct addrinfo *rp, unsigned int flags)
{
int sfd, on = 1;
@@ -109,6 +152,39 @@ static int socket_helper(const struct addrinfo *rp, unsigned int flags)
return sfd;
}
+#ifdef HAVE_LIBSCTP
+/* Fill buf with a string representation of the address set, in the form:
+ * buf_len == 0: "()"
+ * buf_len == 1: "hostA"
+ * buf_len >= 2: (hostA|hostB|...|...)
+ */
+static int multiaddr_snprintf(char* buf, size_t buf_len, const char **hosts, size_t host_cnt)
+{
+ int len = 0, offset = 0, rem = buf_len;
+ int ret, i;
+ char *after;
+
+ if (buf_len < 3)
+ return -EINVAL;
+
+ if (host_cnt != 1) {
+ ret = snprintf(buf, rem, "(");
+ if (ret < 0)
+ return ret;
+ OSMO_SNPRINTF_RET(ret, rem, offset, len);
+ }
+ for (i = 0; i < host_cnt; i++) {
+ if (host_cnt == 1)
+ after = "";
+ else
+ after = (i == (host_cnt - 1)) ? ")" : "|";
+ ret = snprintf(buf + offset, rem, "%s%s", hosts[i] ? : "0.0.0.0", after);
+ OSMO_SNPRINTF_RET(ret, rem, offset, len);
+ }
+
+ return len;
+}
+#endif /* HAVE_LIBSCTP */
static int osmo_sock_init_tail(int fd, uint16_t type, unsigned int flags)
{
@@ -199,12 +275,6 @@ int osmo_sock_init2(uint16_t family, uint16_t type, uint8_t proto,
return -EINVAL;
for (rp = result; rp != NULL; rp = rp->ai_next) {
- /* Workaround for glibc again */
- if (type == SOCK_RAW) {
- rp->ai_socktype = SOCK_RAW;
- rp->ai_protocol = proto;
- }
-
sfd = socket_helper(rp, flags);
if (sfd < 0)
continue;
@@ -253,12 +323,6 @@ int osmo_sock_init2(uint16_t family, uint16_t type, uint8_t proto,
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
- /* Workaround for glibc again */
- if (type == SOCK_RAW) {
- rp->ai_socktype = SOCK_RAW;
- rp->ai_protocol = proto;
- }
-
if (sfd < 0) {
sfd = socket_helper(rp, flags);
if (sfd < 0)
@@ -297,6 +361,229 @@ int osmo_sock_init2(uint16_t family, uint16_t type, uint8_t proto,
return sfd;
}
+#ifdef HAVE_LIBSCTP
+
+
+/* Build array of addresses taking first addrinfo result of the requested family
+ * for each host in hosts. addrs4 or addrs6 are filled based on family type. */
+static int addrinfo_to_sockaddr(uint16_t family, const struct addrinfo **result,
+ const char **hosts, int host_cont,
+ struct sockaddr_in *addrs4, struct sockaddr_in6 *addrs6) {
+ size_t host_idx;
+ const struct addrinfo *rp;
+ OSMO_ASSERT(family == AF_INET || family == AF_INET6);
+
+ for (host_idx = 0; host_idx < host_cont; host_idx++) {
+ for (rp = result[host_idx]; rp != NULL; rp = rp->ai_next) {
+ if (rp->ai_family != family)
+ continue;
+ if (family == AF_INET)
+ memcpy(&addrs4[host_idx], rp->ai_addr, sizeof(addrs4[host_idx]));
+ else
+ memcpy(&addrs6[host_idx], rp->ai_addr, sizeof(addrs6[host_idx]));
+ break;
+ }
+ if (!rp) { /* No addr could be bound for this host! */
+ LOGP(DLGLOBAL, LOGL_ERROR, "No suitable remote address found for host: %s\n",
+ hosts[host_idx]);
+ return -ENODEV;
+ }
+ }
+ return 0;
+}
+
+/*! Initialize a socket (including bind and/or connect) with multiple local or remote addresses.
+ * \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC
+ * \param[in] type Socket type like SOCK_DGRAM, SOCK_STREAM
+ * \param[in] proto Protocol like IPPROTO_TCP, IPPROTO_UDP
+ * \param[in] local_hosts array of char pointers (strings), each containing local host name or IP address in string form
+ * \param[in] local_hosts_cnt length of local_hosts (in items)
+ * \param[in] local_port local port number in host byte order
+ * \param[in] remote_host array of char pointers (strings), each containing remote host name or IP address in string form
+ * \param[in] remote_hosts_cnt length of remote_hosts (in items)
+ * \param[in] remote_port remote port number in host byte order
+ * \param[in] flags flags like \ref OSMO_SOCK_F_CONNECT
+ * \returns socket file descriptor on success; negative on error
+ *
+ * This function is similar to \ref osmo_sock_init2(), but can be passed an
+ * array of local or remote addresses for protocols supporting multiple
+ * addresses per socket, like SCTP (currently only one supported). This function
+ * should not be used by protocols not supporting this kind of features, but
+ * rather \ref osmo_sock_init2() should be used instead.
+ * See \ref osmo_sock_init2() for more information on flags and general behavior.
+ */
+int osmo_sock_init2_multiaddr(uint16_t family, uint16_t type, uint8_t proto,
+ const char **local_hosts, size_t local_hosts_cnt, uint16_t local_port,
+ const char **remote_hosts, size_t remote_hosts_cnt, uint16_t remote_port,
+ unsigned int flags)
+
+{
+ struct addrinfo *result[OSMO_SOCK_MAX_ADDRS];
+ int sfd = -1, rc, on = 1;
+ int i;
+ struct sockaddr_in addrs4[OSMO_SOCK_MAX_ADDRS];
+ struct sockaddr_in6 addrs6[OSMO_SOCK_MAX_ADDRS];
+ struct sockaddr *addrs;
+ char strbuf[512];
+
+ /* TODO: So far this function is only aimed for SCTP, but could be
+ reused in the future for other protocols with multi-addr support */
+ if (proto != IPPROTO_SCTP)
+ return -ENOTSUP;
+
+ /* TODO: Let's not support AF_UNSPEC for now. sctp_bindx() actually
+ supports binding both types of addresses on a AF_INET6 soscket, but
+ that would mean we could get both AF_INET and AF_INET6 addresses for
+ each host, and makes complexity of this function increase a lot since
+ we'd need to find out which subsets to use, use v4v6 mapped socket,
+ etc. */
+ if (family == AF_UNSPEC)
+ return -ENOTSUP;
+
+ if ((flags & (OSMO_SOCK_F_BIND | OSMO_SOCK_F_CONNECT)) == 0) {
+ LOGP(DLGLOBAL, LOGL_ERROR, "invalid: you have to specify either "
+ "BIND or CONNECT flags\n");
+ return -EINVAL;
+ }
+
+ if (((flags & OSMO_SOCK_F_BIND) && !local_hosts_cnt) ||
+ ((flags & OSMO_SOCK_F_CONNECT) && !remote_hosts_cnt) ||
+ local_hosts_cnt > OSMO_SOCK_MAX_ADDRS ||
+ remote_hosts_cnt > OSMO_SOCK_MAX_ADDRS)
+ return -EINVAL;
+
+ /* figure out local side of socket */
+ if (flags & OSMO_SOCK_F_BIND) {
+ rc = addrinfo_helper_multi(result, family, type, proto, local_hosts,
+ local_hosts_cnt, local_port, true);
+ if (rc < 0)
+ return -EINVAL;
+
+ /* Since addrinfo_helper sets ai_family, socktype and
+ ai_protocol in hints, we know all results will use same
+ values, so simply pick the first one and pass it to create
+ the socket:
+ */
+ sfd = socket_helper(result[0], flags);
+ if (sfd < 0) {
+ for (i = 0; i < local_hosts_cnt; i++)
+ freeaddrinfo(result[i]);
+ return sfd;
+ }
+
+ /* Since so far we only allow IPPROTO_SCTP in this function,
+ no need to check below for "proto != IPPROTO_UDP || flags & OSMO_SOCK_F_UDP_REUSEADDR" */
+ rc = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR,
+ &on, sizeof(on));
+ if (rc < 0) {
+ multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt);
+ LOGP(DLGLOBAL, LOGL_ERROR,
+ "cannot setsockopt socket:"
+ " %s:%u: %s\n",
+ strbuf, local_port,
+ strerror(errno));
+ for (i = 0; i < local_hosts_cnt; i++)
+ freeaddrinfo(result[i]);
+ close(sfd);
+ return rc;
+ }
+
+ /* Build array of addresses taking first of same family for each host.
+ TODO: Ideally we should use backtracking storing last used
+ indexes and trying next combination if connect() fails .*/
+ rc = addrinfo_to_sockaddr(family, (const struct addrinfo **)result,
+ local_hosts, local_hosts_cnt, addrs4, addrs6);
+ if (rc < 0) {
+ for (i = 0; i < local_hosts_cnt; i++)
+ freeaddrinfo(result[i]);
+ close(sfd);
+ return -ENODEV;
+ }
+
+ if (family == AF_INET)
+ addrs = (struct sockaddr *)addrs4;
+ else
+ addrs = (struct sockaddr *)addrs6;
+ if (sctp_bindx(sfd, addrs, local_hosts_cnt, SCTP_BINDX_ADD_ADDR) == -1) {
+ multiaddr_snprintf(strbuf, sizeof(strbuf), local_hosts, local_hosts_cnt);
+ LOGP(DLGLOBAL, LOGL_NOTICE, "unable to bind socket: %s:%u: %s\n",
+ strbuf, local_port, strerror(errno));
+ for (i = 0; i < local_hosts_cnt; i++)
+ freeaddrinfo(result[i]);
+ close(sfd);
+ return -ENODEV;
+ }
+ for (i = 0; i < local_hosts_cnt; i++)
+ freeaddrinfo(result[i]);
+ }
+
+ /* Reached this point, if OSMO_SOCK_F_BIND then sfd is valid (>=0) or it
+ was already closed and func returned. If OSMO_SOCK_F_BIND is not
+ set, then sfd = -1 */
+
+ /* figure out remote side of socket */
+ if (flags & OSMO_SOCK_F_CONNECT) {
+ rc = addrinfo_helper_multi(result, family, type, proto, remote_hosts,
+ remote_hosts_cnt, remote_port, false);
+ if (rc < 0) {
+ if (sfd >= 0)
+ close(sfd);
+ return -EINVAL;
+ }
+
+ if (sfd < 0) {
+ /* Since addrinfo_helper sets ai_family, socktype and
+ ai_protocol in hints, we know all results will use same
+ values, so simply pick the first one and pass it to create
+ the socket:
+ */
+ sfd = socket_helper(result[0], flags);
+ if (sfd < 0) {
+ for (i = 0; i < remote_hosts_cnt; i++)
+ freeaddrinfo(result[i]);
+ return sfd;
+ }
+ }
+
+ /* Build array of addresses taking first of same family for each host.
+ TODO: Ideally we should use backtracking storing last used
+ indexes and trying next combination if connect() fails .*/
+ rc = addrinfo_to_sockaddr(family, (const struct addrinfo **)result,
+ remote_hosts, remote_hosts_cnt, addrs4, addrs6);
+ if (rc < 0) {
+ for (i = 0; i < remote_hosts_cnt; i++)
+ freeaddrinfo(result[i]);
+ close(sfd);
+ return -ENODEV;
+ }
+
+ if (family == AF_INET)
+ addrs = (struct sockaddr *)addrs4;
+ else
+ addrs = (struct sockaddr *)addrs6;
+ rc = sctp_connectx(sfd, addrs, remote_hosts_cnt, NULL);
+ if (rc != 0 && errno != EINPROGRESS) {
+ multiaddr_snprintf(strbuf, sizeof(strbuf), remote_hosts, remote_hosts_cnt);
+ LOGP(DLGLOBAL, LOGL_ERROR, "unable to connect socket: %s:%u: %s\n",
+ strbuf, remote_port, strerror(errno));
+ for (i = 0; i < remote_hosts_cnt; i++)
+ freeaddrinfo(result[i]);
+ close(sfd);
+ return -ENODEV;
+ }
+ for (i = 0; i < remote_hosts_cnt; i++)
+ freeaddrinfo(result[i]);
+ }
+
+ rc = osmo_sock_init_tail(sfd, type, flags);
+ if (rc < 0) {
+ close(sfd);
+ sfd = -1;
+ }
+
+ return sfd;
+}
+#endif /* HAVE_LIBSCTP */
/*! Initialize a socket (including bind/connect)
* \param[in] family Address Family like AF_INET, AF_INET6, AF_UNSPEC
@@ -332,12 +619,6 @@ int osmo_sock_init(uint16_t family, uint16_t type, uint8_t proto,
}
for (rp = result; rp != NULL; rp = rp->ai_next) {
- /* Workaround for glibc again */
- if (type == SOCK_RAW) {
- rp->ai_socktype = SOCK_RAW;
- rp->ai_protocol = proto;
- }
-
sfd = socket_helper(rp, flags);
if (sfd == -1)
continue;
diff --git a/src/tdef.c b/src/tdef.c
index 40a99002..71a33158 100644
--- a/src/tdef.c
+++ b/src/tdef.c
@@ -25,6 +25,7 @@
*/
#include <limits.h>
+#include <errno.h>
#include <osmocom/core/fsm.h>
#include <osmocom/core/tdef.h>
@@ -135,14 +136,22 @@ static unsigned long osmo_tdef_round(unsigned long val, enum osmo_tdef_unit from
/*! Set all osmo_tdef values to the default_val.
* It is convenient to define a tdefs array by setting only the default_val, and calling osmo_tdefs_reset() once for
- * program startup. (See also osmo_tdef_vty_init())
+ * program startup. (See also osmo_tdef_vty_init()).
+ * During call to this function, default values are verified to be inside valid range; process is aborted otherwise.
* \param[in] tdefs Array of timer definitions, last entry being fully zero.
*/
void osmo_tdefs_reset(struct osmo_tdef *tdefs)
{
struct osmo_tdef *t;
- osmo_tdef_for_each(t, tdefs)
+ osmo_tdef_for_each(t, tdefs) {
+ if (!osmo_tdef_val_in_range(t, t->default_val)) {
+ char range_str[64];
+ osmo_tdef_range_str_buf(range_str, sizeof(range_str), t);
+ osmo_panic("%s:%d Timer " OSMO_T_FMT " contains default value %lu not in range %s\n",
+ __FILE__, __LINE__, OSMO_T_FMT_ARGS(t->T), t->default_val, range_str);
+ }
t->val = t->default_val;
+ }
}
/*! Return the value of a T timer from a list of osmo_tdef, in the given unit.
@@ -210,6 +219,67 @@ struct osmo_tdef *osmo_tdef_get_entry(struct osmo_tdef *tdefs, int T)
return NULL;
}
+/*! Set value in entry matching T, converting val from val_unit to unit of T.
+ * The converted value is rounded up to the next integer value of T's unit and clamped to ULONG_MAX, or 0 if val == 0.
+ * \param[in] tdefs Array of timer definitions, last entry being fully zero.
+ * \param[in] T Timer number to set the value for.
+ * \param[in] val The new timer value to set.
+ * \param[in] val_unit Units of value in parameter val.
+ * \return 0 on success, negative on error.
+ */
+int osmo_tdef_set(struct osmo_tdef *tdefs, int T, unsigned long val, enum osmo_tdef_unit val_unit)
+{
+ unsigned long new_val;
+ struct osmo_tdef *t = osmo_tdef_get_entry(tdefs, T);
+ if (!t)
+ return -EEXIST;
+
+ new_val = osmo_tdef_round(val, val_unit, t->unit);
+ if (!osmo_tdef_val_in_range(t, new_val))
+ return -ERANGE;
+
+ t->val = new_val;
+ return 0;
+}
+
+/*! Check if value new_val is in range of valid possible values for timer entry tdef.
+ * \param[in] tdef Timer entry from a timer definition table.
+ * \param[in] new_val The value whose validity to check, in units as per this timer entry.
+ * \return true if inside range, false otherwise.
+ */
+bool osmo_tdef_val_in_range(struct osmo_tdef *tdef, unsigned long new_val)
+{
+ return new_val >= tdef->min_val && (!tdef->max_val || new_val <= tdef->max_val);
+}
+
+/*! Write string representation of osmo_tdef range into buf.
+ * \param[in] buf The buffer where the string representation is stored.
+ * \param[in] buf_len Length of buffer in bytes.
+ * \param[in] tdef Timer entry from a timer definition table.
+ * \return The number of characters printed on success (or number of characters
+ * which would have been written to the final string if enough space
+ * had been available), negative on error. See snprintf().
+ */
+int osmo_tdef_range_str_buf(char *buf, size_t buf_len, struct osmo_tdef *t)
+{
+ int ret, len = 0, offset = 0, rem = buf_len;
+
+ buf[0] = '\0';
+ ret = snprintf(buf + offset, rem, "[%lu .. ", t->min_val);
+ if (ret < 0)
+ return ret;
+ OSMO_SNPRINTF_RET(ret, rem, offset, len);
+
+ if (t->max_val)
+ ret = snprintf(buf + offset, rem, "%lu]", t->max_val);
+ else
+ ret = snprintf(buf + offset, rem, "inf]");
+ if (ret < 0)
+ return ret;
+ OSMO_SNPRINTF_RET(ret, rem, offset, len);
+ return len;
+}
+
/*! Using osmo_tdef for osmo_fsm_inst: find a given state's osmo_tdef_state_timeout entry.
*
* The timeouts_array shall contain exactly 32 elements, regardless whether only some of them are actually populated
diff --git a/src/vty/command.c b/src/vty/command.c
index 454a10c3..6a9d18af 100644
--- a/src/vty/command.c
+++ b/src/vty/command.c
@@ -47,6 +47,11 @@ Boston, MA 02110-1301, USA. */
#include <osmocom/core/talloc.h>
#include <osmocom/core/utils.h>
+#ifndef MAXPATHLEN
+ #define MAXPATHLEN 4096
+#endif
+
+
/*! \addtogroup command
* @{
* VTY command handling
@@ -2626,8 +2631,7 @@ int config_from_file(struct vty *vty, FILE * fp)
ret = cmd_execute_command_strict(vline, vty, NULL);
cmd_free_strvec(vline);
- if (ret != CMD_SUCCESS && ret != CMD_WARNING
- && ret != CMD_ERR_NOTHING_TODO) {
+ if (ret != CMD_SUCCESS && ret != CMD_ERR_NOTHING_TODO) {
if (indent) {
talloc_free(indent);
indent = NULL;
@@ -2657,6 +2661,10 @@ int config_from_file(struct vty *vty, FILE * fp)
indent = NULL;
}
}
+ /* Make sure we call go_parent_cb for all remaining indent levels at the end of file */
+ while (vty_parent(vty))
+ vty_go_parent(vty);
+
return CMD_SUCCESS;
return_invalid_indent:
@@ -2922,9 +2930,10 @@ static int write_config_file(const char *config_file, char **outpath)
/* Write current configuration into file. */
DEFUN(config_write_file,
config_write_file_cmd,
- "write file",
+ "write file [PATH]",
"Write running configuration to memory, network, or terminal\n"
- "Write to configuration file\n")
+ "Write to configuration file\n"
+ "Set file path to store the config, or replace if already exists\n")
{
char *failed_file;
int rc;
@@ -2938,6 +2947,9 @@ DEFUN(config_write_file,
}
}
+ if (argc == 1)
+ host_config_set(argv[0]);
+
if (host.config == NULL) {
vty_out(vty, "Can't save to configuration file, using vtysh.%s",
VTY_NEWLINE);
diff --git a/src/vty/logging_vty.c b/src/vty/logging_vty.c
index d639a8f7..6d908d9e 100644
--- a/src/vty/logging_vty.c
+++ b/src/vty/logging_vty.c
@@ -101,6 +101,25 @@ struct log_target *log_target_create_vty(struct vty *vty)
return target;
}
+/*! Get tgt with log lock acquired, return and release lock with warning if tgt
+ * is not found. Lock must be released later with log_tgt_mutex_unlock().
+ */
+#define ACQUIRE_VTY_LOG_TGT_WITH_LOCK(vty, tgt) \
+ do { \
+ log_tgt_mutex_lock(); \
+ tgt = osmo_log_vty2tgt(vty); \
+ if (!(tgt)) { \
+ log_tgt_mutex_unlock(); \
+ return CMD_WARNING; \
+ } \
+ } while (0)
+
+#define RET_WITH_UNLOCK(ret) \
+ do { \
+ log_tgt_mutex_unlock(); \
+ return (ret); \
+ } while (0)
+
DEFUN(enable_logging,
enable_logging_cmd,
"logging enable",
@@ -118,11 +137,16 @@ DEFUN(enable_logging,
conn->dbg = log_target_create_vty(vty);
if (!conn->dbg)
return CMD_WARNING;
-
+ log_tgt_mutex_lock();
log_add_target(conn->dbg);
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
+/*! Get log target associated to VTY console.
+ * \param[in] vty Log target type
+ * \returns Log target (if logging enabled), NULL otherwise
+ * Must be called with mutex osmo_log_tgt_mutex held, see log_tgt_mutex_lock.
+ */
struct log_target *osmo_log_vty2tgt(struct vty *vty)
{
struct telnet_connection *conn;
@@ -146,13 +170,12 @@ DEFUN(logging_fltr_all,
"Only print messages matched by other filters\n"
"Bypass filter and print all messages\n")
{
- struct log_target *tgt = osmo_log_vty2tgt(vty);
+ struct log_target *tgt;
- if (!tgt)
- return CMD_WARNING;
+ ACQUIRE_VTY_LOG_TGT_WITH_LOCK(vty, tgt);
log_set_all_filter(tgt, atoi(argv[0]));
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
DEFUN(logging_use_clr,
@@ -162,13 +185,12 @@ DEFUN(logging_use_clr,
"Don't use color for printing messages\n"
"Use color for printing messages\n")
{
- struct log_target *tgt = osmo_log_vty2tgt(vty);
+ struct log_target *tgt;
- if (!tgt)
- return CMD_WARNING;
+ ACQUIRE_VTY_LOG_TGT_WITH_LOCK(vty, tgt);
log_set_use_color(tgt, atoi(argv[0]));
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
DEFUN(logging_prnt_timestamp,
@@ -178,13 +200,12 @@ DEFUN(logging_prnt_timestamp,
"Don't prefix each log message\n"
"Prefix each log message with current timestamp\n")
{
- struct log_target *tgt = osmo_log_vty2tgt(vty);
+ struct log_target *tgt;
- if (!tgt)
- return CMD_WARNING;
+ ACQUIRE_VTY_LOG_TGT_WITH_LOCK(vty, tgt);
log_set_print_timestamp(tgt, atoi(argv[0]));
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
DEFUN(logging_prnt_ext_timestamp,
@@ -195,13 +216,12 @@ DEFUN(logging_prnt_ext_timestamp,
"Don't prefix each log message\n"
"Prefix each log message with current timestamp with YYYYMMDDhhmmssnnn\n")
{
- struct log_target *tgt = osmo_log_vty2tgt(vty);
+ struct log_target *tgt;
- if (!tgt)
- return CMD_WARNING;
+ ACQUIRE_VTY_LOG_TGT_WITH_LOCK(vty, tgt);
log_set_print_extended_timestamp(tgt, atoi(argv[0]));
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
DEFUN(logging_prnt_cat,
@@ -212,13 +232,11 @@ DEFUN(logging_prnt_cat,
"Don't prefix each log message\n"
"Prefix each log message with category/subsystem name\n")
{
- struct log_target *tgt = osmo_log_vty2tgt(vty);
-
- if (!tgt)
- return CMD_WARNING;
+ struct log_target *tgt;
+ ACQUIRE_VTY_LOG_TGT_WITH_LOCK(vty, tgt);
log_set_print_category(tgt, atoi(argv[0]));
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
DEFUN(logging_prnt_cat_hex,
@@ -229,13 +247,12 @@ DEFUN(logging_prnt_cat_hex,
"Don't prefix each log message\n"
"Prefix each log message with category/subsystem nr in hex ('<000b>')\n")
{
- struct log_target *tgt = osmo_log_vty2tgt(vty);
+ struct log_target *tgt;
- if (!tgt)
- return CMD_WARNING;
+ ACQUIRE_VTY_LOG_TGT_WITH_LOCK(vty, tgt);
log_set_print_category_hex(tgt, atoi(argv[0]));
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
DEFUN(logging_prnt_level,
@@ -246,13 +263,12 @@ DEFUN(logging_prnt_level,
"Don't prefix each log message\n"
"Prefix each log message with the log level name\n")
{
- struct log_target *tgt = osmo_log_vty2tgt(vty);
+ struct log_target *tgt;
- if (!tgt)
- return CMD_WARNING;
+ ACQUIRE_VTY_LOG_TGT_WITH_LOCK(vty, tgt);
log_set_print_level(tgt, atoi(argv[0]));
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
static const struct value_string logging_print_file_args[] = {
@@ -273,17 +289,16 @@ DEFUN(logging_prnt_file,
"Log source file info at the end of a log line. If omitted, log source file info just"
" before the log text.\n")
{
- struct log_target *tgt = osmo_log_vty2tgt(vty);
+ struct log_target *tgt;
- if (!tgt)
- return CMD_WARNING;
+ ACQUIRE_VTY_LOG_TGT_WITH_LOCK(vty, tgt);
log_set_print_filename2(tgt, get_string_value(logging_print_file_args, argv[0]));
if (argc > 1)
log_set_print_filename_pos(tgt, LOG_FILENAME_POS_LINE_END);
else
log_set_print_filename_pos(tgt, LOG_FILENAME_POS_HEADER_END);
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
static void add_category_strings(char **cmd_str_p, char **doc_str_p,
@@ -332,27 +347,26 @@ DEFUN(logging_level,
NULL, /* cmdstr is dynamically set in logging_vty_add_cmds(). */
NULL) /* same thing for helpstr. */
{
+ struct log_target *tgt;
int category = log_parse_category(argv[0]);
int level = log_parse_level(argv[1]);
- struct log_target *tgt = osmo_log_vty2tgt(vty);
- if (!tgt)
- return CMD_WARNING;
+ ACQUIRE_VTY_LOG_TGT_WITH_LOCK(vty, tgt);
if (level < 0) {
vty_out(vty, "Invalid level `%s'%s", argv[1], VTY_NEWLINE);
- return CMD_WARNING;
+ RET_WITH_UNLOCK(CMD_WARNING);
}
if (category < 0) {
vty_out(vty, "Invalid category `%s'%s", argv[0], VTY_NEWLINE);
- return CMD_WARNING;
+ RET_WITH_UNLOCK(CMD_WARNING);
}
tgt->categories[category].enabled = 1;
tgt->categories[category].loglevel = level;
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
DEFUN(logging_level_set_all, logging_level_set_all_cmd,
@@ -362,12 +376,11 @@ DEFUN(logging_level_set_all, logging_level_set_all_cmd,
" to take back these changes -- each category is set to the given level, period.\n"
LOG_LEVEL_STRS)
{
- struct log_target *tgt = osmo_log_vty2tgt(vty);
+ struct log_target *tgt;
int level = log_parse_level(argv[0]);
int i;
- if (!tgt)
- return CMD_WARNING;
+ ACQUIRE_VTY_LOG_TGT_WITH_LOCK(vty, tgt);
for (i = 0; i < osmo_log_info->num_cat; i++) {
struct log_category *cat = &tgt->categories[i];
@@ -378,7 +391,7 @@ DEFUN(logging_level_set_all, logging_level_set_all_cmd,
cat->enabled = 1;
cat->loglevel = level;
}
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
/* logging level (<categories>) everything */
@@ -394,23 +407,25 @@ DEFUN(logging_level_force_all, logging_level_force_all_cmd,
"logging level force-all " LOG_LEVEL_ARGS,
LOGGING_STR LEVEL_STR FORCE_ALL_STR LOG_LEVEL_STRS)
{
- struct log_target *tgt = osmo_log_vty2tgt(vty);
+ struct log_target *tgt;
int level = log_parse_level(argv[0]);
- if (!tgt)
- return CMD_WARNING;
+
+ ACQUIRE_VTY_LOG_TGT_WITH_LOCK(vty, tgt);
+
log_set_log_level(tgt, level);
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
DEFUN(no_logging_level_force_all, no_logging_level_force_all_cmd,
"no logging level force-all",
NO_STR LOGGING_STR LEVEL_STR NO_FORCE_ALL_STR)
{
- struct log_target *tgt = osmo_log_vty2tgt(vty);
- if (!tgt)
- return CMD_WARNING;
+ struct log_target *tgt;
+
+ ACQUIRE_VTY_LOG_TGT_WITH_LOCK(vty, tgt);
+
log_set_log_level(tgt, 0);
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
/* 'logging level all (debug|...|fatal)' */
@@ -438,13 +453,12 @@ DEFUN(logging_set_category_mask,
" " OSMO_STRINGIFY(LOGL_FATAL) "=" OSMO_STRINGIFY_VAL(LOGL_FATAL)
"\n")
{
- struct log_target *tgt = osmo_log_vty2tgt(vty);
+ struct log_target *tgt;
- if (!tgt)
- return CMD_WARNING;
+ ACQUIRE_VTY_LOG_TGT_WITH_LOCK(vty, tgt);
log_parse_category_mask(tgt, argv[0]);
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
ALIAS_DEPRECATED(logging_set_category_mask,
@@ -462,17 +476,16 @@ DEFUN(diable_logging,
LOGGING_STR
"Disables logging to this vty\n")
{
- struct log_target *tgt = osmo_log_vty2tgt(vty);
+ struct log_target *tgt;
struct telnet_connection *conn = (struct telnet_connection *) vty->priv;
- if (!tgt)
- return CMD_WARNING;
+ ACQUIRE_VTY_LOG_TGT_WITH_LOCK(vty, tgt);
log_del_target(tgt);
talloc_free(tgt);
conn->dbg = NULL;
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
static void vty_print_logtarget(struct vty *vty, const struct log_info *info,
@@ -517,14 +530,12 @@ DEFUN(show_logging_vty,
SHOW_STR SHOW_LOG_STR
"Show current logging configuration for this vty\n")
{
- struct log_target *tgt = osmo_log_vty2tgt(vty);
+ struct log_target *tgt;
- if (!tgt)
- return CMD_WARNING;
+ ACQUIRE_VTY_LOG_TGT_WITH_LOCK(vty, tgt);
vty_print_logtarget(vty, osmo_log_info, tgt);
-
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
DEFUN(show_alarms,
@@ -535,11 +546,14 @@ DEFUN(show_alarms,
{
int i, num_alarms;
struct osmo_strrb *rb;
- struct log_target *tgt = log_target_find(LOG_TGT_TYPE_STRRB, NULL);
+ struct log_target *tgt;
+
+ log_tgt_mutex_lock();
+ tgt = log_target_find(LOG_TGT_TYPE_STRRB, NULL);
if (!tgt) {
vty_out(vty, "%% No alarms, run 'log alarms <2-32700>'%s",
VTY_NEWLINE);
- return CMD_WARNING;
+ RET_WITH_UNLOCK(CMD_WARNING);
}
rb = tgt->tgt_rb.rb;
@@ -550,8 +564,7 @@ DEFUN(show_alarms,
for (i = 0; i < num_alarms; i++)
vty_out(vty, "%% %s%s", osmo_strrb_get_nth(rb, i),
VTY_NEWLINE);
-
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
gDEFUN(cfg_description, cfg_description_cmd,
@@ -625,6 +638,7 @@ static int _cfg_log_syslog(struct vty *vty, int facility)
{
struct log_target *tgt;
+ log_tgt_mutex_lock();
/* First delete the old syslog target, if any */
tgt = log_target_find(LOG_TGT_TYPE_SYSLOG, NULL);
if (tgt)
@@ -633,14 +647,14 @@ static int _cfg_log_syslog(struct vty *vty, int facility)
tgt = log_target_create_syslog(host.app_info->name, 0, facility);
if (!tgt) {
vty_out(vty, "%% Unable to open syslog%s", VTY_NEWLINE);
- return CMD_WARNING;
+ RET_WITH_UNLOCK(CMD_WARNING);
}
log_add_target(tgt);
vty->index = tgt;
vty->node = CFG_LOG_NODE;
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
DEFUN(cfg_log_syslog_local, cfg_log_syslog_local_cmd,
@@ -700,16 +714,17 @@ DEFUN(cfg_no_log_syslog, cfg_no_log_syslog_cmd,
{
struct log_target *tgt;
+ log_tgt_mutex_lock();
tgt = log_target_find(LOG_TGT_TYPE_SYSLOG, NULL);
if (!tgt) {
vty_out(vty, "%% No syslog target found%s",
VTY_NEWLINE);
- return CMD_WARNING;
+ RET_WITH_UNLOCK(CMD_WARNING);
}
log_target_destroy(tgt);
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
#endif /* HAVE_SYSLOG_H */
@@ -721,6 +736,7 @@ DEFUN(cfg_log_gsmtap, cfg_log_gsmtap_cmd,
const char *hostname = argc ? argv[0] : "127.0.0.1";
struct log_target *tgt;
+ log_tgt_mutex_lock();
tgt = log_target_find(LOG_TGT_TYPE_GSMTAP, hostname);
if (!tgt) {
tgt = log_target_create_gsmtap(hostname, GSMTAP_UDP_PORT,
@@ -729,7 +745,7 @@ DEFUN(cfg_log_gsmtap, cfg_log_gsmtap_cmd,
if (!tgt) {
vty_out(vty, "%% Unable to create GSMTAP log for %s%s",
hostname, VTY_NEWLINE);
- return CMD_WARNING;
+ RET_WITH_UNLOCK(CMD_WARNING);
}
log_add_target(tgt);
}
@@ -737,7 +753,7 @@ DEFUN(cfg_log_gsmtap, cfg_log_gsmtap_cmd,
vty->index = tgt;
vty->node = CFG_LOG_NODE;
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
DEFUN(cfg_log_stderr, cfg_log_stderr_cmd,
@@ -746,13 +762,14 @@ DEFUN(cfg_log_stderr, cfg_log_stderr_cmd,
{
struct log_target *tgt;
+ log_tgt_mutex_lock();
tgt = log_target_find(LOG_TGT_TYPE_STDERR, NULL);
if (!tgt) {
tgt = log_target_create_stderr();
if (!tgt) {
vty_out(vty, "%% Unable to create stderr log%s",
VTY_NEWLINE);
- return CMD_WARNING;
+ RET_WITH_UNLOCK(CMD_WARNING);
}
log_add_target(tgt);
}
@@ -760,7 +777,7 @@ DEFUN(cfg_log_stderr, cfg_log_stderr_cmd,
vty->index = tgt;
vty->node = CFG_LOG_NODE;
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
DEFUN(cfg_no_log_stderr, cfg_no_log_stderr_cmd,
@@ -769,15 +786,16 @@ DEFUN(cfg_no_log_stderr, cfg_no_log_stderr_cmd,
{
struct log_target *tgt;
+ log_tgt_mutex_lock();
tgt = log_target_find(LOG_TGT_TYPE_STDERR, NULL);
if (!tgt) {
vty_out(vty, "%% No stderr logging active%s", VTY_NEWLINE);
- return CMD_WARNING;
+ RET_WITH_UNLOCK(CMD_WARNING);
}
log_target_destroy(tgt);
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
DEFUN(cfg_log_file, cfg_log_file_cmd,
@@ -787,13 +805,14 @@ DEFUN(cfg_log_file, cfg_log_file_cmd,
const char *fname = argv[0];
struct log_target *tgt;
+ log_tgt_mutex_lock();
tgt = log_target_find(LOG_TGT_TYPE_FILE, fname);
if (!tgt) {
tgt = log_target_create_file(fname);
if (!tgt) {
vty_out(vty, "%% Unable to create file `%s'%s",
fname, VTY_NEWLINE);
- return CMD_WARNING;
+ RET_WITH_UNLOCK(CMD_WARNING);
}
log_add_target(tgt);
}
@@ -801,7 +820,7 @@ DEFUN(cfg_log_file, cfg_log_file_cmd,
vty->index = tgt;
vty->node = CFG_LOG_NODE;
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
@@ -812,16 +831,17 @@ DEFUN(cfg_no_log_file, cfg_no_log_file_cmd,
const char *fname = argv[0];
struct log_target *tgt;
+ log_tgt_mutex_lock();
tgt = log_target_find(LOG_TGT_TYPE_FILE, fname);
if (!tgt) {
vty_out(vty, "%% No such log file `%s'%s",
fname, VTY_NEWLINE);
- return CMD_WARNING;
+ RET_WITH_UNLOCK(CMD_WARNING);
}
log_target_destroy(tgt);
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
DEFUN(cfg_log_alarms, cfg_log_alarms_cmd,
@@ -832,6 +852,8 @@ DEFUN(cfg_log_alarms, cfg_log_alarms_cmd,
struct log_target *tgt;
unsigned int rbsize = atoi(argv[0]);
+
+ log_tgt_mutex_lock();
tgt = log_target_find(LOG_TGT_TYPE_STRRB, NULL);
if (tgt)
log_target_destroy(tgt);
@@ -840,14 +862,14 @@ DEFUN(cfg_log_alarms, cfg_log_alarms_cmd,
if (!tgt) {
vty_out(vty, "%% Unable to create osmo_strrb (size %u)%s",
rbsize, VTY_NEWLINE);
- return CMD_WARNING;
+ RET_WITH_UNLOCK(CMD_WARNING);
}
log_add_target(tgt);
vty->index = tgt;
vty->node = CFG_LOG_NODE;
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
DEFUN(cfg_no_log_alarms, cfg_no_log_alarms_cmd,
@@ -856,15 +878,16 @@ DEFUN(cfg_no_log_alarms, cfg_no_log_alarms_cmd,
{
struct log_target *tgt;
+ log_tgt_mutex_lock();
tgt = log_target_find(LOG_TGT_TYPE_STRRB, NULL);
if (!tgt) {
vty_out(vty, "%% No osmo_strrb target found%s", VTY_NEWLINE);
- return CMD_WARNING;
+ RET_WITH_UNLOCK(CMD_WARNING);
}
log_target_destroy(tgt);
- return CMD_SUCCESS;
+ RET_WITH_UNLOCK(CMD_SUCCESS);
}
static int config_write_log_single(struct vty *vty, struct log_target *tgt)
@@ -962,27 +985,27 @@ static int config_write_log_single(struct vty *vty, struct log_target *tgt)
static int config_write_log(struct vty *vty)
{
+ log_tgt_mutex_lock();
struct log_target *dbg = vty->index;
llist_for_each_entry(dbg, &osmo_log_target_list, entry)
config_write_log_single(vty, dbg);
+ log_tgt_mutex_unlock();
return 1;
}
static int log_deprecated_func(struct cmd_element *cmd, struct vty *vty, int argc, const char *argv[])
{
vty_out(vty, "%% Ignoring deprecated '%s'%s", cmd->string, VTY_NEWLINE);
- return CMD_WARNING;
+ return CMD_SUCCESS; /* Otherwise the process would terminate */
}
void logging_vty_add_deprecated_subsys(void *ctx, const char *name)
{
struct cmd_element *cmd = talloc_zero(ctx, struct cmd_element);
OSMO_ASSERT(cmd);
- cmd->string = talloc_asprintf(cmd, "logging level %s (debug|info|notice|error|fatal)",
- name);
- printf("%s\n", cmd->string);
+ cmd->string = talloc_asprintf(cmd, "logging level %s " LOG_LEVEL_ARGS, name);
cmd->func = log_deprecated_func;
cmd->doc = LEVEL_STR
"Deprecated Category\n";
diff --git a/src/vty/tdef_vty.c b/src/vty/tdef_vty.c
index eb05c3c5..4549a61c 100644
--- a/src/vty/tdef_vty.c
+++ b/src/vty/tdef_vty.c
@@ -115,12 +115,22 @@ unsigned long osmo_tdef_vty_parse_val_arg(const char *val_arg, unsigned long def
*/
int osmo_tdef_vty_set_cmd(struct vty *vty, struct osmo_tdef *tdefs, const char **args)
{
+ unsigned long new_val;
const char *T_arg = args[0];
const char *val_arg = args[1];
struct osmo_tdef *t = osmo_tdef_vty_parse_T_arg(vty, tdefs, T_arg);
if (!t)
return CMD_WARNING;
- t->val = osmo_tdef_vty_parse_val_arg(val_arg, t->default_val);
+ new_val = osmo_tdef_vty_parse_val_arg(val_arg, t->default_val);
+
+ if (!osmo_tdef_val_in_range(t, new_val)) {
+ char range_str[64];
+ osmo_tdef_range_str_buf(range_str, sizeof(range_str), t);
+ vty_out(vty, "%% Timer " OSMO_T_FMT " value %lu is out of range %s%s",
+ OSMO_T_FMT_ARGS(t->T), new_val, range_str, VTY_NEWLINE);
+ return CMD_WARNING;
+ }
+ t->val = new_val;
return CMD_SUCCESS;
}
@@ -161,18 +171,29 @@ int osmo_tdef_vty_show_cmd(struct vty *vty, struct osmo_tdef *tdefs, const char
*/
void osmo_tdef_vty_out_one_va(struct vty *vty, struct osmo_tdef *t, const char *prefix_fmt, va_list va)
{
+ char range_str[64];
+
if (!t) {
vty_out(vty, "%% Error: no such timer%s", VTY_NEWLINE);
return;
}
if (prefix_fmt)
vty_out_va(vty, prefix_fmt, va);
- vty_out(vty, OSMO_T_FMT " = %lu%s%s\t%s (default: %lu%s%s)%s",
- OSMO_T_FMT_ARGS(t->T), t->val,
- t->unit == OSMO_TDEF_CUSTOM ? "" : " ", t->unit == OSMO_TDEF_CUSTOM ? "" : osmo_tdef_unit_name(t->unit),
- t->desc, t->default_val,
- t->unit == OSMO_TDEF_CUSTOM ? "" : " ", t->unit == OSMO_TDEF_CUSTOM ? "" : osmo_tdef_unit_name(t->unit),
- VTY_NEWLINE);
+
+ vty_out(vty, OSMO_T_FMT " = %lu", OSMO_T_FMT_ARGS(t->T), t->val);
+ if (t->unit != OSMO_TDEF_CUSTOM)
+ vty_out(vty, " %s", osmo_tdef_unit_name(t->unit));
+
+ vty_out(vty, "\t%s (default: %lu", t->desc, t->default_val);
+ if (t->unit != OSMO_TDEF_CUSTOM)
+ vty_out(vty, " %s", osmo_tdef_unit_name(t->unit));
+
+ if (t->min_val || t->max_val) {
+ osmo_tdef_range_str_buf(range_str, sizeof(range_str), t);
+ vty_out(vty, ", range: %s", range_str);
+ }
+
+ vty_out(vty, ")%s", VTY_NEWLINE);
}
/*! Write to VTY the current status of one timer.
diff --git a/src/vty/vty.c b/src/vty/vty.c
index a96d86ce..cd252ad1 100644
--- a/src/vty/vty.c
+++ b/src/vty/vty.c
@@ -67,6 +67,11 @@
#include <osmocom/vty/buffer.h>
#include <osmocom/core/talloc.h>
+#ifndef MAXPATHLEN
+ #define MAXPATHLEN 4096
+#endif
+
+
/* \addtogroup vty
* @{
* \file vty.c */
@@ -205,6 +210,9 @@ void vty_close(struct vty *vty)
{
int i;
+ /* VTY_CLOSED is handled by the telnet_interface */
+ vty_event(VTY_CLOSED, vty->fd, vty);
+
if (vty->obuf) {
/* Flush buffer. */
buffer_flush_all(vty->obuf, vty->fd);
@@ -223,7 +231,7 @@ void vty_close(struct vty *vty)
vector_unset(vtyvec, vty->fd);
/* Close socket. */
- if (vty->fd > 0) {
+ if (vty->fd > 0 && vty->fd != fileno(stderr)) {
close(vty->fd);
vty->fd = -1;
}
@@ -236,9 +244,6 @@ void vty_close(struct vty *vty)
/* Check configure. */
vty_config_unlock(vty);
- /* VTY_CLOSED is handled by the telnet_interface */
- vty_event(VTY_CLOSED, vty->fd, vty);
-
/* OK free vty. */
talloc_free(vty);
}
@@ -1463,11 +1468,15 @@ vty_read_file(FILE *confp, void *priv)
struct vty *vty;
vty = vty_new();
- vty->fd = 0;
vty->type = VTY_FILE;
vty->node = CONFIG_NODE;
vty->priv = priv;
+ /* By default, write to stderr. Otherwise, during parsing of the logging
+ * configuration, all invocations to vty_out() would make the process
+ * write() to its own stdin (fd=0)! */
+ vty->fd = fileno(stderr);
+
ret = config_from_file(vty, confp);
if (ret != CMD_SUCCESS) {
diff --git a/tests/Makefile.am b/tests/Makefile.am
index a8a06c53..e8e4dee1 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1,7 +1,7 @@
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include
-AM_CFLAGS = -Wall $(TALLOC_CFLAGS)
+AM_CFLAGS = -Wall $(TALLOC_CFLAGS) $(PTHREAD_CFLAGS)
AM_LDFLAGS = -no-install
-LDADD = $(top_builddir)/src/libosmocore.la $(TALLOC_LIBS)
+LDADD = $(top_builddir)/src/libosmocore.la $(TALLOC_LIBS) $(PTHREAD_LIBS)
if ENABLE_SERCOM_STUB
noinst_LIBRARIES = libsercomstub.a
@@ -32,6 +32,8 @@ check_PROGRAMS = timer/timer_test sms/sms_test ussd/ussd_test \
tdef/tdef_vty_test_dynamic \
sockaddr_str/sockaddr_str_test \
use_count/use_count_test \
+ context/context_test \
+ gsm0502/gsm0502_test \
$(NULL)
if ENABLE_MSGFILE
@@ -107,6 +109,9 @@ bits_bitfield_test_SOURCES = bits/bitfield_test.c
conv_conv_test_SOURCES = conv/conv_test.c conv/conv.c
conv_conv_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libgsmint.la
+gsm0502_gsm0502_test_SOURCES = gsm0502/gsm0502_test.c
+gsm0502_gsm0502_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la
+
conv_conv_gsm0503_test_SOURCES = conv/conv_gsm0503_test.c conv/conv.c conv/gsm0503_test_vectors.c
conv_conv_gsm0503_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libgsmint.la
conv_conv_gsm0503_test_CPPFLAGS = $(AM_CPPFLAGS) -I$(top_srcdir)/tests/conv
@@ -251,6 +256,9 @@ sockaddr_str_sockaddr_str_test_LDADD = $(LDADD)
use_count_use_count_test_SOURCES = use_count/use_count_test.c
use_count_use_count_test_LDADD = $(LDADD)
+context_context_test_SOURCES = context/context_test.c
+context_context_test_LDADD = $(LDADD)
+
# The `:;' works around a Bash 3.2 bug when the output is not writeable.
$(srcdir)/package.m4: $(top_srcdir)/configure.ac
:;{ \
@@ -289,6 +297,7 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \
vty/fail_not_de-indented.cfg \
vty/fail_tabs_and_spaces.cfg \
vty/fail_too_much_indent.cfg \
+ vty/fail_cmd_ret_warning.cfg \
vty/ok.cfg \
vty/ok_empty_parent.cfg \
vty/ok_ignore_blank.cfg \
@@ -297,6 +306,7 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \
vty/ok_more_spaces.cfg \
vty/ok_tabs_and_spaces.cfg \
vty/ok_tabs.cfg \
+ vty/ok_deprecated_logging.cfg \
comp128/comp128_test.ok bits/bitfield_test.ok \
utils/utils_test.ok utils/utils_test.err stats/stats_test.ok \
bitvec/bitvec_test.ok msgb/msgb_test.ok bits/bitcomp_test.ok \
@@ -322,6 +332,8 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \
tdef/tdef_vty_test_dynamic.vty \
sockaddr_str/sockaddr_str_test.ok \
use_count/use_count_test.ok use_count/use_count_test.err \
+ context/context_test.ok \
+ gsm0502/gsm0502_test.ok \
$(NULL)
DISTCLEANFILES = atconfig atlocal conv/gsm0503_test_vectors.c
@@ -331,7 +343,7 @@ noinst_HEADERS = conv/conv.h
TESTSUITE = $(srcdir)/testsuite
check-local: atconfig $(TESTSUITE)
- cat /proc/cpuinfo
+ [ -e /proc/cpuinfo ] && cat /proc/cpuinfo
$(SHELL) '$(TESTSUITE)' $(TESTSUITEFLAGS)
$(MAKE) $(AM_MAKEFLAGS) ext-tests
diff --git a/tests/codec/codec_ecu_fr_test.c b/tests/codec/codec_ecu_fr_test.c
index 3561c442..4040ce94 100644
--- a/tests/codec/codec_ecu_fr_test.c
+++ b/tests/codec/codec_ecu_fr_test.c
@@ -113,6 +113,8 @@ void test_fr_concealment(void)
int i, rc;
int j = 0;
+ printf("=> Testing FR concealment (simple, consecutive bad frames)\n");
+
while (sample_frame_hex[j] != NULL) {
/* Parse frame from string to hex */
osmo_hexparse(sample_frame_hex[j], frame, GSM_FR_BYTES);
@@ -140,6 +142,46 @@ void test_fr_concealment(void)
}
}
+/* Same as test_fr_concealment() but using generic core */
+void test_fr_concealment_core(void)
+{
+ struct osmo_ecu_state *state = osmo_ecu_init(NULL, OSMO_ECU_CODEC_FR);
+ uint8_t frame[GSM_FR_BYTES];
+ uint64_t xmaxc[4];
+ int i, rc;
+ int j = 0;
+
+ printf("=> Testing FR concealment (simple, consecutive bad frames)\n");
+
+ while (sample_frame_hex[j] != NULL) {
+ /* Parse frame from string to hex */
+ osmo_hexparse(sample_frame_hex[j], frame, GSM_FR_BYTES);
+ parse_xmaxc_frame(frame, xmaxc);
+ printf("Start with: %s, XMAXC: [%"PRIx64", %"PRIx64", %"PRIx64", %"PRIx64"]\n",
+ sample_frame_hex[j], xmaxc[0], xmaxc[1], xmaxc[2], xmaxc[3]);
+
+ /* Reset the ECU with the proposed known good frame */
+ osmo_ecu_frame_in(state, false, frame, GSM_FR_BYTES);
+
+ /* Now pretend that we do not receive any good frames anymore */
+ for (i = 0; i < 20; i++) {
+
+ rc = osmo_ecu_frame_out(state, frame);
+ OSMO_ASSERT(rc == GSM_FR_BYTES);
+ parse_xmaxc_frame(frame, xmaxc);
+
+ printf("conceal: %02i, result: %s XMAXC: [%"PRIx64", %"PRIx64", %"PRIx64", %"PRIx64"]\n",
+ i, osmo_hexdump_nospc(frame, GSM_FR_BYTES),
+ xmaxc[0], xmaxc[1], xmaxc[2], xmaxc[3]);
+ }
+
+ /* Go to the next frame */
+ j++;
+ }
+
+ osmo_ecu_destroy(state);
+}
+
/* Simulate a real life situation: voice frames with a few dropouts */
void test_fr_concealment_realistic()
{
@@ -148,6 +190,8 @@ void test_fr_concealment_realistic()
unsigned int frame_len;
int rc, i = 0;
+ printf("\n=> Testing FR concealment (realistic, various bad frames)\n");
+
while (fr_frames_hex[i] != NULL) {
/* Debug print */
printf("Frame No. %03i:\n", i);
@@ -174,11 +218,55 @@ void test_fr_concealment_realistic()
}
}
+/* Simulate a real life situation: voice frames with a few dropouts, using generic core */
+void test_fr_concealment_realistic_core()
+{
+ struct osmo_ecu_state *state = osmo_ecu_init(NULL, OSMO_ECU_CODEC_FR);
+ uint8_t frame[GSM_FR_BYTES];
+ unsigned int frame_len;
+ int rc, i = 0;
+
+ printf("\n=> Testing FR concealment (realistic, using ECU abstraction)\n");
+
+ OSMO_ASSERT(state);
+
+ while (fr_frames_hex[i] != NULL) {
+ /* Debug print */
+ printf("Frame No. %03i:\n", i);
+
+ /* Good or bad frame? */
+ frame_len = strlen(fr_frames_hex[i]) / 2;
+ if (frame_len == GSM_FR_BYTES) {
+ printf(" * input: %s\n", fr_frames_hex[i]);
+ osmo_hexparse(fr_frames_hex[i], frame, GSM_FR_BYTES);
+ osmo_ecu_frame_in(state, false, frame, GSM_FR_BYTES);
+ } else {
+ printf(" * input: (bad)\n");
+ memset(frame, 0x00, GSM_FR_BYTES);
+ osmo_ecu_frame_in(state, true, frame, 0);
+ rc = osmo_ecu_frame_out(state, frame);
+ OSMO_ASSERT(rc == GSM_FR_BYTES);
+ }
+
+ /* Print result */
+ printf(" * output: %s\n",
+ osmo_hexdump_nospc(frame, GSM_FR_BYTES));
+
+ /* Go to the next frame */
+ i++;
+ }
+
+ osmo_ecu_destroy(state);
+}
+
+
int main(int argc, char **argv)
{
/* Perform actual tests */
test_fr_concealment();
+ test_fr_concealment_core();
test_fr_concealment_realistic();
+ test_fr_concealment_realistic_core();
return 0;
}
diff --git a/tests/codec/codec_ecu_fr_test.ok b/tests/codec/codec_ecu_fr_test.ok
index 3cc35af9..d925e285 100644
--- a/tests/codec/codec_ecu_fr_test.ok
+++ b/tests/codec/codec_ecu_fr_test.ok
@@ -1,3 +1,4 @@
+=> Testing FR concealment (simple, consecutive bad frames)
Start with: d9ec9be212901f802335598c501f805bad3d4ba01f809b69df5a501f809cd1b4da, XMAXC: [3f, 3f, 3f, 3f]
conceal: 00, result: d9ec9be212901f802335598c501f805bad3d4ba01f809b69df5a501f809cd1b4da XMAXC: [3f, 3f, 3f, 3f]
conceal: 01, result: d9ec9be212901d802335598c501d805bad3d4ba01d809b69df5a501d809cd1b4da XMAXC: [3b, 3b, 3b, 3b]
@@ -40,6 +41,224 @@ conceal: 16, result: d0000000000000000000000000000000000000000000000000000000000
conceal: 17, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
conceal: 18, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
conceal: 19, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
+=> Testing FR concealment (simple, consecutive bad frames)
+Start with: d9ec9be212901f802335598c501f805bad3d4ba01f809b69df5a501f809cd1b4da, XMAXC: [3f, 3f, 3f, 3f]
+conceal: 00, result: d9ec9be212901f802335598c501f805bad3d4ba01f809b69df5a501f809cd1b4da XMAXC: [3f, 3f, 3f, 3f]
+conceal: 01, result: d9ec9be212901d802335598c501d805bad3d4ba01d809b69df5a501d809cd1b4da XMAXC: [3b, 3b, 3b, 3b]
+conceal: 02, result: d9ec9be212901b802335598c501b805bad3d4ba01b809b69df5a501b809cd1b4da XMAXC: [37, 37, 37, 37]
+conceal: 03, result: d9ec9be2129019802335598c5019805bad3d4ba019809b69df5a5019809cd1b4da XMAXC: [33, 33, 33, 33]
+conceal: 04, result: d9ec9be2129017802335598c5017805bad3d4ba017809b69df5a5017809cd1b4da XMAXC: [2f, 2f, 2f, 2f]
+conceal: 05, result: d9ec9be2129015802335598c5015805bad3d4ba015809b69df5a5015809cd1b4da XMAXC: [2b, 2b, 2b, 2b]
+conceal: 06, result: d9ec9be2129013802335598c5013805bad3d4ba013809b69df5a5013809cd1b4da XMAXC: [27, 27, 27, 27]
+conceal: 07, result: d9ec9be2129011802335598c5011805bad3d4ba011809b69df5a5011809cd1b4da XMAXC: [23, 23, 23, 23]
+conceal: 08, result: d9ec9be212900f802335598c500f805bad3d4ba00f809b69df5a500f809cd1b4da XMAXC: [1f, 1f, 1f, 1f]
+conceal: 09, result: d9ec9be212900d802335598c500d805bad3d4ba00d809b69df5a500d809cd1b4da XMAXC: [1b, 1b, 1b, 1b]
+conceal: 10, result: d9ec9be212900b802335598c500b805bad3d4ba00b809b69df5a500b809cd1b4da XMAXC: [17, 17, 17, 17]
+conceal: 11, result: d9ec9be2129009802335598c5009805bad3d4ba009809b69df5a5009809cd1b4da XMAXC: [13, 13, 13, 13]
+conceal: 12, result: d9ec9be2129007802335598c5007805bad3d4ba007809b69df5a5007809cd1b4da XMAXC: [f, f, f, f]
+conceal: 13, result: d9ec9be2129005802335598c5005805bad3d4ba005809b69df5a5005809cd1b4da XMAXC: [b, b, b, b]
+conceal: 14, result: d9ec9be2129003802335598c5003805bad3d4ba003809b69df5a5003809cd1b4da XMAXC: [7, 7, 7, 7]
+conceal: 15, result: d9ec9be2129001802335598c5001805bad3d4ba001809b69df5a5001809cd1b4da XMAXC: [3, 3, 3, 3]
+conceal: 16, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
+conceal: 17, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
+conceal: 18, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
+conceal: 19, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
+Start with: d9ec9be212901d802335598c5013805bad3d4ba01f809b69df5a5019809cd1b4da, XMAXC: [3b, 27, 3f, 33]
+conceal: 00, result: d9ec9be212901d802335598c5013805bad3d4ba01f809b69df5a5019809cd1b4da XMAXC: [3b, 27, 3f, 33]
+conceal: 01, result: d9ec9be212901b802335598c5011805bad3d4ba01d809b69df5a5017809cd1b4da XMAXC: [37, 23, 3b, 2f]
+conceal: 02, result: d9ec9be2129019802335598c500f805bad3d4ba01b809b69df5a5015809cd1b4da XMAXC: [33, 1f, 37, 2b]
+conceal: 03, result: d9ec9be2129017802335598c500d805bad3d4ba019809b69df5a5013809cd1b4da XMAXC: [2f, 1b, 33, 27]
+conceal: 04, result: d9ec9be2129015802335598c500b805bad3d4ba017809b69df5a5011809cd1b4da XMAXC: [2b, 17, 2f, 23]
+conceal: 05, result: d9ec9be2129013802335598c5009805bad3d4ba015809b69df5a500f809cd1b4da XMAXC: [27, 13, 2b, 1f]
+conceal: 06, result: d9ec9be2129011802335598c5007805bad3d4ba013809b69df5a500d809cd1b4da XMAXC: [23, f, 27, 1b]
+conceal: 07, result: d9ec9be212900f802335598c5005805bad3d4ba011809b69df5a500b809cd1b4da XMAXC: [1f, b, 23, 17]
+conceal: 08, result: d9ec9be212900d802335598c5003805bad3d4ba00f809b69df5a5009809cd1b4da XMAXC: [1b, 7, 1f, 13]
+conceal: 09, result: d9ec9be212900b802335598c5001805bad3d4ba00d809b69df5a5007809cd1b4da XMAXC: [17, 3, 1b, f]
+conceal: 10, result: d9ec9be2129009802335598c5000005bad3d4ba00b809b69df5a5005809cd1b4da XMAXC: [13, 0, 17, b]
+conceal: 11, result: d9ec9be2129007802335598c5000005bad3d4ba009809b69df5a5003809cd1b4da XMAXC: [f, 0, 13, 7]
+conceal: 12, result: d9ec9be2129005802335598c5000005bad3d4ba007809b69df5a5001809cd1b4da XMAXC: [b, 0, f, 3]
+conceal: 13, result: d9ec9be2129003802335598c5000005bad3d4ba005809b69df5a5000009cd1b4da XMAXC: [7, 0, b, 0]
+conceal: 14, result: d9ec9be2129001802335598c5000005bad3d4ba003809b69df5a5000009cd1b4da XMAXC: [3, 0, 7, 0]
+conceal: 15, result: d9ec9be2129000002335598c5000005bad3d4ba001809b69df5a5000009cd1b4da XMAXC: [0, 0, 3, 0]
+conceal: 16, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
+conceal: 17, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
+conceal: 18, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
+conceal: 19, result: d00000000000000000000000000000000000000000000000000000000000000000 XMAXC: [0, 0, 0, 0]
+
+=> Testing FR concealment (realistic, various bad frames)
+Frame No. 000:
+ * input: d9aa93ae63de00471a91b95b8660471392b4a2daa037628f391c624039258dc723
+ * output: d9aa93ae63de00471a91b95b8660471392b4a2daa037628f391c624039258dc723
+Frame No. 001:
+ * input: d8eb83699a66c036ec89b7246e6034dc8d48948620589b7256e3a6603b2371b8da
+ * output: d8eb83699a66c036ec89b7246e6034dc8d48948620589b7256e3a6603b2371b8da
+Frame No. 002:
+ * input: d967abaa1cbe4035238da6ace4c036d46ec69ba600391c4eb8a2b040591c6a3924
+ * output: d967abaa1cbe4035238da6ace4c036d46ec69ba600391c4eb8a2b040591c6a3924
+Frame No. 003:
+ * input: d8e8a42662c240472469b91bd2e0452291b6dba600495b8e38dcb020491a71c91b
+ * output: d8e8a42662c240472469b91bd2e0452291b6dba600495b8e38dcb020491a71c91b
+Frame No. 004:
+ * input: da2aac1ddbb00036e46e26dcec6039138db923822047137248e3560048e38dc8e3
+ * output: da2aac1ddbb00036e46e26dcec6039138db923822047137248e3560048e38dc8e3
+Frame No. 005:
+ * input: d929ab2a9b5240395b6dc72ba020469c8d551c5440349c9148e36a4036a372471b
+ * output: d929ab2a9b5240395b6dc72ba020469c8d551c5440349c9148e36a4036a372471b
+Frame No. 006:
+ * input: d9eb93215bb8a0271c69c724682036db71c71a94a0372491b72bee4044eb71b923
+ * output: d9eb93215bb8a0271c69c724682036db71c71a94a0372491b72bee4044eb71b923
+Frame No. 007:
+ * input: d9ab9aa19abc40391b6e5ae2ee40471b91c6dbe820492291b8e4b84036e47238db
+ * output: d9ab9aa19abc40391b6e5ae2ee40471b91c6dbe820492291b8e4b84036e47238db
+Frame No. 008:
+ * input: d96b9be9db782044e371b55cb200389491c69b8ea034e271c8d3808038ec6db8e3
+ * output: d96b9be9db782044e371b55cb200389491c69b8ea034e271c8d3808038ec6db8e3
+Frame No. 009:
+ * input: d9aa9365e3f060375c6db6ebc4c02764b1c51b78a0571c91a723de6049248dc8dd
+ * output: d9aa9365e3f060375c6db6ebc4c02764b1c51b78a0571c91a723de6049248dc8dd
+Frame No. 010:
+ * input: (bad)
+ * output: d9aa9365e3f060375c6db6ebc4c02764b1c51b78a0571c91a723de6049248dc8dd
+Frame No. 011:
+ * input: d9ea9c219ce60046e38d3724e0c034e56e36eb7e0038d471b8dcb260491b8dbb23
+ * output: d9ea9c219ce60046e38d3724e0c034e56e36eb7e0038d471b8dcb260491b8dbb23
+Frame No. 012:
+ * input: d9e89be9d9e0a0391b6dd6a4624029247138e3a2a04713922524de0036db69d91c
+ * output: d9e89be9d9e0a0391b6dd6a4624029247138e3a2a04713922524de0036db69d91c
+Frame No. 013:
+ * input: d9699422a2b6a048dd90c91c6a802b6259395c8880575b4a58e4ac20269d7248d4
+ * output: d9699422a2b6a048dd90c91c6a802b6259395c8880575b4a58e4ac20269d7248d4
+Frame No. 014:
+ * input: d967ac5b1baae0371c71b8ab9c804a9e8e58a55a8038626ec8dcb640395c7244dc
+ * output: d967ac5b1baae0371c71b8ab9c804a9e8e58a55a8038626ec8dcb640395c7244dc
+Frame No. 015:
+ * input: d9e8a3e262e68027638db52b88a038634e471a7ec049136e3b1bc8402923adcad2
+ * output: d9e8a3e262e68027638db52b88a038634e471a7ec049136e3b1bc8402923adcad2
+Frame No. 016:
+ * input: d8eab36e1bbe0046e34d491b608035137658d3524044e48e375cdac0472b9238d4
+ * output: d8eab36e1bbe0046e34d491b608035137658d3524044e48e375cdac0472b9238d4
+Frame No. 017:
+ * input: d9689ba5e3d260491b516adb5e4027256e27227ee0351c8e549a5c60492471971b
+ * output: d9689ba5e3d260491b516adb5e4027256e27227ee0351c8e549a5c60492471971b
+Frame No. 018:
+ * input: (bad)
+ * output: d9689ba5e3d260491b516adb5e4027256e27227ee0351c8e549a5c60492471971b
+Frame No. 019:
+ * input: (bad)
+ * output: d00000000000000000000000000000000000000000000000000000000000000000
+Frame No. 020:
+ * input: d8e6a2e1d3d2605b1376c8d35280392451391cbc80392a71b6db8aa049238dc8ab
+ * output: d8e6a2e1d3d2605b1376c8d35280392451391cbc80392a71b6db8aa049238dc8ab
+Frame No. 021:
+ * input: d9a87ba1a3982048eb8a471cac00472b4e391bbc40292489b71cc200495b8d3ae3
+ * output: d9a87ba1a3982048eb8a471cac00472b4e391bbc40292489b71cc200495b8d3ae3
+Frame No. 022:
+ * input: d9278b2a1ba4c0475b8dc722d6e0491b5228da70204ae36dc71d94a056a29236e3
+ * output: d9278b2a1ba4c0475b8dc722d6e0491b5228da70204ae36dc71d94a056a29236e3
+Frame No. 023:
+ * input: d9ec9be2129520392335598c50c04b5bad3d4ba680789b69df5a5aa0469cd1b4da
+ * output: d9ec9be2129520392335598c50c04b5bad3d4ba680789b69df5a5aa0469cd1b4da
+Frame No. 024:
+ * input: d8ea932623e660669b8e4a9dd8a03aa32a76e466e028d396cc9bbe4047256dc8e5
+ * output: d8ea932623e660669b8e4a9dd8a03aa32a76e466e028d396cc9bbe4047256dc8e5
+Frame No. 025:
+ * input: d96a94215aa0403aab713f22e8e024e68db91ab6a027abd1a55b6e804aec9146e4
+ * output: d96a94215aa0403aab713f22e8e024e68db91ab6a027abd1a55b6e804aec9146e4
+Frame No. 026:
+ * input: d867ac21e270a0350d6ac91a724037247246d2a6c0396c89d6dc562049244e48d5
+ * output: d867ac21e270a0350d6ac91a724037247246d2a6c0396c89d6dc562049244e48d5
+Frame No. 027:
+ * input: d8a9b460d3b48026a4ad471b7c20452491b69bbc803ae48db722ee00292491a8db
+ * output: d8a9b460d3b48026a4ad471b7c20452491b69bbc803ae48db722ee00292491a8db
+Frame No. 028:
+ * input: d928a3e1d3b24036e37244abf02047634d371b74c047637148a29ac03b234e38e3
+ * output: d928a3e1d3b24036e37244abf02047634d371b74c047637148a29ac03b234e38e3
+Frame No. 029:
+ * input: d9ab9b21d2e0c0471c693aec54e044dbae46dc7c20391badb724ee8038e469bb15
+ * output: d9ab9b21d2e0c0471c693aec54e044dbae46dc7c20391badb724ee8038e469bb15
+Frame No. 030:
+ * input: d9a99361a276403b1a6ad6dcd40026e489c8e3bc40371c4dc564e2c036e28eb963
+ * output: d9a99361a276403b1a6ad6dcd40026e489c8e3bc40371c4dc564e2c036e28eb963
+Frame No. 031:
+ * input: (bad)
+ * output: d9a99361a276403b1a6ad6dcd40026e489c8e3bc40371c4dc564e2c036e28eb963
+Frame No. 032:
+ * input: (bad)
+ * output: d00000000000000000000000000000000000000000000000000000000000000000
+Frame No. 033:
+ * input: (bad)
+ * output: d00000000000000000000000000000000000000000000000000000000000000000
+Frame No. 034:
+ * input: (bad)
+ * output: d00000000000000000000000000000000000000000000000000000000000000000
+Frame No. 035:
+ * input: (bad)
+ * output: d00000000000000000000000000000000000000000000000000000000000000000
+Frame No. 036:
+ * input: (bad)
+ * output: d00000000000000000000000000000000000000000000000000000000000000000
+Frame No. 037:
+ * input: d92c8b6d5aee4034ebb22724862047145634a5c0a038e371b8e4a880485c89dd25
+ * output: d92c8b6d5aee4034ebb22724862047145634a5c0a038e371b8e4a880485c89dd25
+Frame No. 038:
+ * input: d8e78b29e3c6c038dba9d91beca04723ad491cda80471471b6ec7ae03b1396b91b
+ * output: d8e78b29e3c6c038dba9d91beca04723ad491cda80471471b6ec7ae03b1396b91b
+Frame No. 039:
+ * input: d8a78b25e37a0022dd8a46dc68a0351bad391bde2046e56dd8dc96c038e396d89b
+ * output: d8a78b25e37a0022dd8a46dc68a0351bad391bde2046e56dd8dc96c038e396d89b
+Frame No. 040:
+ * input: d8a88c255ab6e038e38e48dbde8038ad8dc8db8ec0376372b564b44038e49234dc
+ * output: d8a88c255ab6e038e38e48dbde8038ad8dc8db8ec0376372b564b44038e49234dc
+Frame No. 041:
+ * input: d9708ce6a39ce049646646a2c1a0272496b29a66c037db562863ace0795b55b2e3
+ * output: d9708ce6a39ce049646646a2c1a0272496b29a66c037db562863ace0795b55b2e3
+Frame No. 042:
+ * input: d8ee9bea5ae4003ae371b713eae05adc91995a5ea064dcc9571e786026ed51c52c
+ * output: d8ee9bea5ae4003ae371b713eae05adc91995a5ea064dcc9571e786026ed51c52c
+Frame No. 043:
+ * input: d9299421d2944036ed69b8e572a048e36d551cd480571d4ec95be680356c69c763
+ * output: d9299421d2944036ed69b8e572a048e36d551cd480571d4ec95be680356c69c763
+Frame No. 044:
+ * input: d92aab696190c046e26e392cae0026a376a8dc662048d291b75b54c04ad3ae3b1b
+ * output: d92aab696190c046e26e392cae0026a376a8dc662048d291b75b54c04ad3ae3b1b
+Frame No. 045:
+ * input: d8e7a469627a6038e289cb1baca0569b8db6dddec026dc8e38e5dc803722722d23
+ * output: d8e7a469627a6038e289cb1baca0569b8db6dddec026dc8e38e5dc803722722d23
+Frame No. 046:
+ * input: d8a88c299b64c03a548a58e37420272c6dd76b92c0471c9236dbc0e0551c71c713
+ * output: d8a88c299b64c03a548a58e37420272c6dd76b92c0471c9236dbc0e0551c71c713
+Frame No. 047:
+ * input: (bad)
+ * output: d8a88c299b64c03a548a58e37420272c6dd76b92c0471c9236dbc0e0551c71c713
+Frame No. 048:
+ * input: d7299c19a3be8024e58ea7a49f20a522963ad976e0a76ecd92b38500cb62aa4c94
+ * output: d7299c19a3be8024e58ea7a49f20a522963ad976e0a76ecd92b38500cb62aa4c94
+Frame No. 049:
+ * input: d7eb6c6262eee02b2c42e79a60a0aa55aed68a7f00ad358e10fad960e55a39396d
+ * output: d7eb6c6262eee02b2c42e79a60a0aa55aed68a7f00ad358e10fad960e55a39396d
+Frame No. 050:
+ * input: d970858dd2ab61d91355ebc15ca1a6a7ca48a05cc0dae66f2523c2a1bad3825daa
+ * output: d970858dd2ab61d91355ebc15ca1a6a7ca48a05cc0dae66f2523c2a1bad3825daa
+Frame No. 051:
+ * input: d8f0844a23ad20da50d6de025e81c37392b9039cc0c764c1bd1e94c1b699736a98
+ * output: d8f0844a23ad20da50d6de025e81c37392b9039cc0c764c1bd1e94c1b699736a98
+Frame No. 052:
+ * input: d9708ce6a39ce049646646a2c1a0272496b29a66c037db562863ace0795b55b2e3
+ * output: d9708ce6a39ce049646646a2c1a0272496b29a66c037db562863ace0795b55b2e3
+Frame No. 053:
+ * input: d9299421d2944036ed69b8e572a048e36d551cd480571d4ec95be680356c69c763
+ * output: d9299421d2944036ed69b8e572a048e36d551cd480571d4ec95be680356c69c763
+Frame No. 054:
+ * input: d9299421d2944036ed69b8e572a048e36d551cd480571d4ec95be680356c69c763
+ * output: d9299421d2944036ed69b8e572a048e36d551cd480571d4ec95be680356c69c763
+Frame No. 055:
+ * input: d9299421d2944036ed69b8e572a048e36d551cd480571d4ec95be680356c69c763
+ * output: d9299421d2944036ed69b8e572a048e36d551cd480571d4ec95be680356c69c763
+Frame No. 056:
+ * input: d2577a1cda50004924924924500049249249245000492492492450004923924924
+ * output: d2577a1cda50004924924924500049249249245000492492492450004923924924
+
+=> Testing FR concealment (realistic, using ECU abstraction)
Frame No. 000:
* input: d9aa93ae63de00471a91b95b8660471392b4a2daa037628f391c624039258dc723
* output: d9aa93ae63de00471a91b95b8660471392b4a2daa037628f391c624039258dc723
diff --git a/tests/context/context_test.c b/tests/context/context_test.c
new file mode 100644
index 00000000..e9a55593
--- /dev/null
+++ b/tests/context/context_test.c
@@ -0,0 +1,84 @@
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+
+#include <sys/eventfd.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/select.h>
+#include <osmocom/core/talloc.h>
+
+static struct osmo_fd g_evfd;
+
+static void *alloc_res_select;
+static void *alloc_res_global;
+
+static int destructor_called;
+
+static int talloc_destructor(void *ptr)
+{
+ printf("destructor was called automatically\n");
+ /* ensure the destructor is only called for the chunk allocated from the
+ * volatile select context */
+ OSMO_ASSERT(ptr == alloc_res_select);
+ destructor_called += 1;
+ return 0;
+}
+
+static int evfd_cb(struct osmo_fd *ofd, unsigned int what)
+{
+ uint64_t rval;
+ int rc;
+
+ rc = read(ofd->fd, &rval, sizeof(rval));
+ OSMO_ASSERT(rc == sizeof(rval));
+
+ printf("allocating from select context\n");
+ alloc_res_select = talloc_named_const(OTC_SELECT, 23, "alloc_select");
+ OSMO_ASSERT(alloc_res_select);
+ talloc_set_destructor(alloc_res_select, talloc_destructor);
+
+ printf("allocating from global context\n");
+ alloc_res_global = talloc_named_const(OTC_GLOBAL, 42, "alloc_global");
+ OSMO_ASSERT(alloc_res_global);
+ talloc_set_destructor(alloc_res_global, talloc_destructor);
+ return 0;
+}
+
+const struct log_info_cat default_categories[] = {
+};
+
+static struct log_info info = {
+ .cat = default_categories,
+ .num_cat = ARRAY_SIZE(default_categories),
+};
+
+int main(int argc, char **argv)
+{
+ int rc;
+
+ osmo_init_logging2(OTC_GLOBAL, &info);
+
+ rc = eventfd(0, 0);
+ OSMO_ASSERT(rc >= 0);
+ osmo_fd_setup(&g_evfd, rc, OSMO_FD_READ, evfd_cb, NULL, 0);
+ osmo_fd_register(&g_evfd);
+
+ /* make sure the select loop will immediately call the callback */
+ uint64_t val = 1;
+ rc = write(g_evfd.fd, &val, sizeof(val));
+ OSMO_ASSERT(rc == sizeof(val));
+
+ /* enter osmo_select_main_ctx() once */
+ printf("entering osmo_select_main\n");
+ osmo_select_main_ctx(1);
+
+ /* the allocation must have happened, and the destructor must have been called
+ * automatically exactly once */
+ OSMO_ASSERT(destructor_called == 1);
+
+ exit(0);
+}
diff --git a/tests/context/context_test.ok b/tests/context/context_test.ok
new file mode 100644
index 00000000..3984a397
--- /dev/null
+++ b/tests/context/context_test.ok
@@ -0,0 +1,4 @@
+entering osmo_select_main
+allocating from select context
+allocating from global context
+destructor was called automatically
diff --git a/tests/fsm/fsm_dealloc_test.c b/tests/fsm/fsm_dealloc_test.c
index ce492056..9a6974d9 100644
--- a/tests/fsm/fsm_dealloc_test.c
+++ b/tests/fsm/fsm_dealloc_test.c
@@ -355,7 +355,7 @@ void obj_term(struct obj *obj)
osmo_fsm_inst_term(obj->fi, OSMO_FSM_TERM_REGULAR, NULL);
}
-void test_dealloc(enum objname trigger, bool by_destroy_event)
+void test_dealloc(enum objname trigger, bool by_destroy_event, void *loop_ctx)
{
struct scene *s = scene_alloc();
const char *label = by_destroy_event ? "destroy-event" : "term";
@@ -383,16 +383,63 @@ void test_dealloc(enum objname trigger, bool by_destroy_event)
LOGP(DLGLOBAL, LOGL_DEBUG, "--- %d objects remain. cleaning up\n", remain);
} else
LOGP(DLGLOBAL, LOGL_DEBUG, "--- all deallocated.\n");
+
+ if (loop_ctx) {
+ fprintf(stderr, "*** loop_ctx contains %zu blocks, deallocating.\n",
+ talloc_total_blocks(loop_ctx));
+ talloc_free_children(loop_ctx);
+ }
+
+ /* Silently free the remaining objects. */
scene_clean(s);
+ if (loop_ctx)
+ talloc_free_children(loop_ctx);
}
-int main(void)
+static void trigger_tests(void *loop_ctx)
{
- enum objname trigger;
size_t ctx_blocks;
size_t ctx_size;
+ enum objname trigger;
int by_destroy_event;
+ ctx_blocks = talloc_total_blocks(ctx);
+ ctx_size = talloc_total_size(ctx);
+
+ for (trigger = 0; trigger < scene_size; trigger++) {
+ for (by_destroy_event = 0; by_destroy_event < 2; by_destroy_event++) {
+ test_dealloc(trigger, (bool)by_destroy_event, loop_ctx);
+
+ if (ctx_blocks != talloc_total_blocks(ctx)
+ || ctx_size != talloc_total_size(ctx)) {
+ talloc_report_full(ctx, stderr);
+ OSMO_ASSERT(false);
+ }
+ }
+ }
+}
+
+void test_osmo_fsm_term_safely()
+{
+ fprintf(stderr, "\n\n%s()\n", __func__);
+ osmo_fsm_term_safely(true);
+ trigger_tests(NULL);
+ osmo_fsm_term_safely(false);
+ fprintf(stderr, "\n\n%s() done\n", __func__);
+}
+
+void test_osmo_fsm_set_dealloc_ctx()
+{
+ fprintf(stderr, "\n\n%s()\n", __func__);
+ void *dealloc_ctx = talloc_named_const(ctx, 0, "fsm_dealloc");
+ osmo_fsm_set_dealloc_ctx(dealloc_ctx);
+ trigger_tests(dealloc_ctx);
+ osmo_fsm_set_dealloc_ctx(NULL);
+ fprintf(stderr, "\n\n%s() done\n", __func__);
+}
+
+int main(void)
+{
ctx = talloc_named_const(NULL, 0, "main");
osmo_init_logging2(ctx, NULL);
@@ -405,22 +452,10 @@ int main(void)
log_set_category_filter(osmo_stderr_target, DLGLOBAL, 1, LOGL_DEBUG);
- osmo_fsm_term_safely(true);
osmo_fsm_register(&test_fsm);
- ctx_blocks = talloc_total_blocks(ctx);
- ctx_size = talloc_total_size(ctx);
-
- for (trigger = 0; trigger < scene_size; trigger++) {
- for (by_destroy_event = 0; by_destroy_event < 2; by_destroy_event++) {
- test_dealloc(trigger, (bool)by_destroy_event);
- if (ctx_blocks != talloc_total_blocks(ctx)
- || ctx_size != talloc_total_size(ctx)) {
- talloc_report_full(ctx, stderr);
- OSMO_ASSERT(false);
- }
- }
- }
+ test_osmo_fsm_term_safely();
+ test_osmo_fsm_set_dealloc_ctx();
talloc_free(ctx);
return 0;
diff --git a/tests/fsm/fsm_dealloc_test.err b/tests/fsm/fsm_dealloc_test.err
index d12c5aa3..ce506e24 100644
--- a/tests/fsm/fsm_dealloc_test.err
+++ b/tests/fsm/fsm_dealloc_test.err
@@ -1,3 +1,6 @@
+
+
+test_osmo_fsm_term_safely()
DLGLOBAL DEBUG scene_alloc()
DLGLOBAL DEBUG test(root){alive}: Allocated
DLGLOBAL DEBUG test(root){alive}: Allocated
@@ -42,14 +45,7 @@ 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(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(root)
@@ -60,23 +56,8 @@ 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}: Ignoring trigger to terminate: already terminating
-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}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 1 (__twig1a.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(root)
@@ -111,14 +92,7 @@ DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup(),_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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(root)
@@ -129,21 +103,8 @@ DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.aliv
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 6 (_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup(),other.other_gone())
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup(),_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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
+DLGLOBAL DEBUG test(other){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(root)
@@ -151,42 +112,25 @@ DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),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 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.cleanup())
DLGLOBAL DEBUG test(other){alive}: cleanup() done
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (_branch1.cleanup())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 3 (_branch1.cleanup(),root.alive(),root.child_gone())
-DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
-DLGLOBAL DEBUG test(root){alive}: No more children
-DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 1 (_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(_branch1){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (root.cleanup())
DLGLOBAL DEBUG test(root){alive}: cleanup()
DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: removing reference root.other[0] -> __twig1a
+DLGLOBAL DEBUG test(__twig1a){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
DLGLOBAL DEBUG test(root){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(root){alive}: Deallocated, including all deferred deallocations
@@ -239,14 +183,7 @@ DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 2 (root.alive(),__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 3 (root.alive(),__twig1b.cleanup(),_branch1.alive())
-DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 4 (root.alive(),__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 3 (root.alive(),__twig1b.cleanup(),_branch1.alive())
-DLGLOBAL DEBUG 2 (root.alive(),__twig1b.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 1 (root.alive())
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(root)
@@ -257,23 +194,8 @@ DLGLOBAL DEBUG 2 (root.alive(),__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 3 (2*root.alive(),__twig1a.cleanup())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
-DLGLOBAL DEBUG 4 (2*root.alive(),__twig1a.cleanup(),root.other_gone())
-DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
-DLGLOBAL DEBUG 3 (2*root.alive(),__twig1a.cleanup())
-DLGLOBAL DEBUG test(root){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 2 (root.alive(),__twig1a.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 3 (root.alive(),__twig1a.cleanup(),_branch1.alive())
-DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 4 (root.alive(),__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 3 (root.alive(),__twig1a.cleanup(),_branch1.alive())
-DLGLOBAL DEBUG test(_branch1){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 2 (root.alive(),__twig1a.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 1 (root.alive())
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(root)
@@ -308,14 +230,7 @@ DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 6 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__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 7 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 8 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup(),_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 7 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG 6 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 5 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(root)
@@ -326,21 +241,8 @@ DLGLOBAL DEBUG 6 (root.alive(),_branch1.cleanup(),other.alive(),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){alive}: Received Event EV_OTHER_GONE
-DLGLOBAL DEBUG 7 (root.alive(),_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
-DLGLOBAL DEBUG 8 (root.alive(),_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup(),other.other_gone())
-DLGLOBAL DEBUG 7 (root.alive(),_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG 6 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 7 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 8 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup(),_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 7 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 6 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
+DLGLOBAL DEBUG test(other){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 5 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(root)
@@ -348,42 +250,25 @@ DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
DLGLOBAL DEBUG 6 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 7 (2*root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 8 (2*root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),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 7 (2*root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
-DLGLOBAL DEBUG 6 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
DLGLOBAL DEBUG 5 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 6 (2*root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.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 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 4 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup())
DLGLOBAL DEBUG test(other){alive}: cleanup() done
DLGLOBAL DEBUG 3 (root.alive(),_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 2 (root.alive(),_branch1.cleanup())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 3 (2*root.alive(),_branch1.cleanup())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 4 (2*root.alive(),_branch1.cleanup(),root.child_gone())
-DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
-DLGLOBAL DEBUG test(root){alive}: No more children
-DLGLOBAL DEBUG 3 (2*root.alive(),_branch1.cleanup())
-DLGLOBAL DEBUG test(root){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 2 (root.alive(),_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
DLGLOBAL DEBUG 1 (root.alive())
DLGLOBAL DEBUG test(_branch1){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 2 (root.alive(),root.cleanup())
DLGLOBAL DEBUG test(root){alive}: cleanup()
DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: removing reference root.other[0] -> __twig1a
+DLGLOBAL DEBUG test(__twig1a){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
DLGLOBAL DEBUG test(root){alive}: cleanup() done
DLGLOBAL DEBUG 1 (root.alive())
DLGLOBAL DEBUG test(root){alive}: Deallocated, including all deferred deallocations
@@ -432,14 +317,7 @@ 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(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(_branch0)
@@ -456,15 +334,7 @@ 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())
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}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 1 (__twig0a.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(_branch0)
@@ -499,14 +369,7 @@ DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 5 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__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 6 (_branch0.cleanup(),other.alive(),other.cleanup(),2*_branch1.alive(),__twig1b.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch0.cleanup(),other.alive(),other.cleanup(),2*_branch1.alive(),__twig1b.cleanup(),_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 6 (_branch0.cleanup(),other.alive(),other.cleanup(),2*_branch1.alive(),__twig1b.cleanup())
-DLGLOBAL DEBUG 5 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1b.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(_branch0)
@@ -534,15 +397,7 @@ DLGLOBAL DEBUG test(root){alive}: cleanup() done
DLGLOBAL DEBUG 6 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG 5 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 6 (_branch0.cleanup(),other.alive(),other.cleanup(),2*_branch1.alive(),__twig1a.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch0.cleanup(),other.alive(),other.cleanup(),2*_branch1.alive(),__twig1a.cleanup(),_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 6 (_branch0.cleanup(),other.alive(),other.cleanup(),2*_branch1.alive(),__twig1a.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 5 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(_branch0)
@@ -550,43 +405,20 @@ DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
DLGLOBAL DEBUG 5 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){alive}: cleanup()
DLGLOBAL DEBUG test(_branch1){alive}: scene forgets _branch1
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 6 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),_branch1.cleanup(),root.alive(),root.child_gone())
-DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
-DLGLOBAL DEBUG test(root){alive}: still exists: child[0]
-DLGLOBAL DEBUG 6 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG 5 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 5 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),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 4 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG 3 (_branch0.cleanup(),other.alive(),other.cleanup())
DLGLOBAL DEBUG test(other){alive}: cleanup() done
DLGLOBAL DEBUG 2 (_branch0.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG 1 (_branch0.cleanup())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 2 (_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 3 (_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}: No more children
-DLGLOBAL DEBUG 2 (_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 1 (_branch0.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 1 (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 0 (-)
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG --- after term cascade:
DLGLOBAL DEBUG --- all deallocated.
@@ -635,14 +467,7 @@ DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 2 (_branch0.alive(),__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 3 (2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 4 (2*_branch0.alive(),__twig0b.cleanup(),_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 3 (2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG 2 (_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 1 (_branch0.alive())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(_branch0)
@@ -659,15 +484,7 @@ DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
DLGLOBAL DEBUG 4 (_branch0.alive(),__twig0a.cleanup(),other.alive(),other.other_gone())
DLGLOBAL DEBUG 3 (_branch0.alive(),__twig0a.cleanup(),other.alive())
DLGLOBAL DEBUG 2 (_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 3 (2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 4 (2*_branch0.alive(),__twig0a.cleanup(),_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 3 (2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 2 (_branch0.alive(),__twig0a.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 1 (_branch0.alive())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(_branch0)
@@ -702,14 +519,7 @@ DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 6 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__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 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),2*_branch1.alive(),__twig1b.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 8 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),2*_branch1.alive(),__twig1b.cleanup(),_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 7 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),2*_branch1.alive(),__twig1b.cleanup())
-DLGLOBAL DEBUG 6 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1b.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 5 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(_branch0)
@@ -737,15 +547,7 @@ DLGLOBAL DEBUG test(root){alive}: cleanup() done
DLGLOBAL DEBUG 7 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG 6 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 7 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),2*_branch1.alive(),__twig1a.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 8 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),2*_branch1.alive(),__twig1a.cleanup(),_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 7 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),2*_branch1.alive(),__twig1a.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 6 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 5 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(_branch0)
@@ -753,43 +555,20 @@ DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
DLGLOBAL DEBUG 6 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){alive}: cleanup()
DLGLOBAL DEBUG test(_branch1){alive}: scene forgets _branch1
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 7 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 8 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),_branch1.cleanup(),root.alive(),root.chil
-DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
-DLGLOBAL DEBUG test(root){alive}: still exists: child[0]
-DLGLOBAL DEBUG 7 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG 6 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
DLGLOBAL DEBUG 5 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 6 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),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 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG 4 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup())
DLGLOBAL DEBUG test(other){alive}: cleanup() done
DLGLOBAL DEBUG 3 (_branch0.alive(),_branch0.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: Deferring: will deallocate with test(_branch0)
DLGLOBAL DEBUG 2 (_branch0.alive(),_branch0.cleanup())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 3 (_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 4 (_branch0.alive(),_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}: No more children
-DLGLOBAL DEBUG 3 (_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 2 (_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
DLGLOBAL DEBUG 1 (_branch0.alive())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 2 (_branch0.alive(),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 1 (_branch0.alive())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG --- after destroy-event cascade:
@@ -876,14 +655,7 @@ 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(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(root)
@@ -894,23 +666,8 @@ 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}: Ignoring trigger to terminate: already terminating
-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}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 1 (__twig1a.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(root)
@@ -945,15 +702,7 @@ DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup(),_branch0.child_gone())
-DLGLOBAL DEBUG test(_branch0){alive}: EV_CHILD_GONE: Dropped reference _branch0.child[1] = __twig0b
-DLGLOBAL DEBUG test(_branch0){alive}: No more children
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(root)
@@ -961,42 +710,25 @@ DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),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 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.cleanup())
DLGLOBAL DEBUG test(other){alive}: cleanup() done
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (_branch1.cleanup())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 3 (_branch1.cleanup(),root.alive(),root.child_gone())
-DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
-DLGLOBAL DEBUG test(root){alive}: No more children
-DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 1 (_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(_branch1){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (root.cleanup())
DLGLOBAL DEBUG test(root){alive}: cleanup()
DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: removing reference root.other[0] -> __twig1a
+DLGLOBAL DEBUG test(__twig1a){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
DLGLOBAL DEBUG test(root){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(root){alive}: Deallocated, including all deferred deallocations
@@ -1086,14 +818,7 @@ 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(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(root)
@@ -1104,23 +829,8 @@ 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}: Ignoring trigger to terminate: already terminating
-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}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 1 (__twig1a.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(root)
@@ -1155,15 +865,7 @@ DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup(),_branch0.child_gone())
-DLGLOBAL DEBUG test(_branch0){alive}: EV_CHILD_GONE: Dropped reference _branch0.child[1] = __twig0b
-DLGLOBAL DEBUG test(_branch0){alive}: No more children
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(root)
@@ -1171,42 +873,25 @@ DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),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 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.cleanup())
DLGLOBAL DEBUG test(other){alive}: cleanup() done
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (_branch1.cleanup())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 3 (_branch1.cleanup(),root.alive(),root.child_gone())
-DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
-DLGLOBAL DEBUG test(root){alive}: No more children
-DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 1 (_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(_branch1){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (root.cleanup())
DLGLOBAL DEBUG test(root){alive}: cleanup()
DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: removing reference root.other[0] -> __twig1a
+DLGLOBAL DEBUG test(__twig1a){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
DLGLOBAL DEBUG test(root){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(root){alive}: Deallocated, including all deferred deallocations
@@ -1285,14 +970,7 @@ 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(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(root)
@@ -1303,23 +981,8 @@ 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}: Ignoring trigger to terminate: already terminating
-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}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 1 (__twig1a.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(root)
@@ -1355,21 +1018,8 @@ DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.aliv
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 6 (_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup(),other.other_gone())
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup(),_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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
+DLGLOBAL DEBUG test(other){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(root)
@@ -1377,42 +1027,25 @@ DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),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 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.cleanup())
DLGLOBAL DEBUG test(other){alive}: cleanup() done
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (_branch1.cleanup())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 3 (_branch1.cleanup(),root.alive(),root.child_gone())
-DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
-DLGLOBAL DEBUG test(root){alive}: No more children
-DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 1 (_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(_branch1){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (root.cleanup())
DLGLOBAL DEBUG test(root){alive}: cleanup()
DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: removing reference root.other[0] -> __twig1a
+DLGLOBAL DEBUG test(__twig1a){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
DLGLOBAL DEBUG test(root){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(root){alive}: Deallocated, including all deferred deallocations
@@ -1495,14 +1128,7 @@ 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(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(root)
@@ -1513,23 +1139,8 @@ 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}: Ignoring trigger to terminate: already terminating
-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}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 1 (__twig1a.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(root)
@@ -1565,21 +1176,8 @@ DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.aliv
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 6 (_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup(),other.other_gone())
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup(),_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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
+DLGLOBAL DEBUG test(other){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(root)
@@ -1587,42 +1185,25 @@ DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),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 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.cleanup())
DLGLOBAL DEBUG test(other){alive}: cleanup() done
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (_branch1.cleanup())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 3 (_branch1.cleanup(),root.alive(),root.child_gone())
-DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
-DLGLOBAL DEBUG test(root){alive}: No more children
-DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 1 (_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(_branch1){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (root.cleanup())
DLGLOBAL DEBUG test(root){alive}: cleanup()
DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: removing reference root.other[0] -> __twig1a
+DLGLOBAL DEBUG test(__twig1a){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
DLGLOBAL DEBUG test(root){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(root){alive}: Deallocated, including all deferred deallocations
@@ -1668,14 +1249,7 @@ 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(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(_branch1)
@@ -1703,15 +1277,7 @@ DLGLOBAL DEBUG test(root){alive}: cleanup() done
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: Deferring: will deallocate with test(_branch1)
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}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 1 (__twig1a.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(_branch1)
@@ -1746,14 +1312,7 @@ DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup(),_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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(_branch1)
@@ -1764,21 +1323,8 @@ DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.aliv
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 6 (_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup(),other.other_gone())
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup(),_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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
+DLGLOBAL DEBUG test(other){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(_branch1)
@@ -1786,43 +1332,20 @@ DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),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 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: Deferring: will deallocate with test(_branch1)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.cleanup())
DLGLOBAL DEBUG test(other){alive}: cleanup() done
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: Deferring: will deallocate with test(_branch1)
DLGLOBAL DEBUG 1 (_branch1.cleanup())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 3 (_branch1.cleanup(),root.alive(),root.child_gone())
-DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
-DLGLOBAL DEBUG test(root){alive}: No more children
-DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 1 (_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 1 (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 0 (-)
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG --- after term cascade:
DLGLOBAL DEBUG --- all deallocated.
@@ -1871,14 +1394,7 @@ DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 2 (_branch1.alive(),__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 3 (2*_branch1.alive(),__twig1b.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 4 (2*_branch1.alive(),__twig1b.cleanup(),_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 3 (2*_branch1.alive(),__twig1b.cleanup())
-DLGLOBAL DEBUG 2 (_branch1.alive(),__twig1b.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 1 (_branch1.alive())
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(_branch1)
@@ -1906,15 +1422,7 @@ DLGLOBAL DEBUG test(root){alive}: cleanup() done
DLGLOBAL DEBUG 3 (_branch1.alive(),__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: Deferring: will deallocate with test(_branch1)
DLGLOBAL DEBUG 2 (_branch1.alive(),__twig1a.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 3 (2*_branch1.alive(),__twig1a.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 4 (2*_branch1.alive(),__twig1a.cleanup(),_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 3 (2*_branch1.alive(),__twig1a.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 2 (_branch1.alive(),__twig1a.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 1 (_branch1.alive())
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(_branch1)
@@ -1949,14 +1457,7 @@ DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 6 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__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 7 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 8 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup(),_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 7 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG 6 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 5 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(_branch1)
@@ -1967,21 +1468,8 @@ DLGLOBAL DEBUG 6 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanu
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 7 (_branch1.alive(),_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
-DLGLOBAL DEBUG 8 (_branch1.alive(),_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup(),other.other_gone())
-DLGLOBAL DEBUG 7 (_branch1.alive(),_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG 6 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 7 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 8 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup(),_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 7 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 6 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
+DLGLOBAL DEBUG test(other){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 5 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(_branch1)
@@ -1989,43 +1477,20 @@ DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
DLGLOBAL DEBUG 6 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 7 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 8 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive(),root.chil
-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 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG 6 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
DLGLOBAL DEBUG 5 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 6 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),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 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: Deferring: will deallocate with test(_branch1)
DLGLOBAL DEBUG 4 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup())
DLGLOBAL DEBUG test(other){alive}: cleanup() done
DLGLOBAL DEBUG 3 (_branch1.alive(),_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: Deferring: will deallocate with test(_branch1)
DLGLOBAL DEBUG 2 (_branch1.alive(),_branch1.cleanup())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 3 (_branch1.alive(),_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 4 (_branch1.alive(),_branch1.cleanup(),root.alive(),root.child_gone())
-DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
-DLGLOBAL DEBUG test(root){alive}: No more children
-DLGLOBAL DEBUG 3 (_branch1.alive(),_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 2 (_branch1.alive(),_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
DLGLOBAL DEBUG 1 (_branch1.alive())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 2 (_branch1.alive(),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 1 (_branch1.alive())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG --- after destroy-event cascade:
@@ -2087,14 +1552,7 @@ DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),__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 (__twig1a.cleanup(),root.alive(),__twig1b.cleanup(),_branch1.alive())
-DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 5 (__twig1a.cleanup(),root.alive(),__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 (__twig1a.cleanup(),root.alive(),__twig1b.cleanup(),_branch1.alive())
-DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),__twig1b.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(__twig1a)
@@ -2129,14 +1587,7 @@ DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 7 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__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 8 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 9 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup(),_bran
-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 8 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG 7 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 6 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(__twig1a)
@@ -2147,21 +1598,8 @@ DLGLOBAL DEBUG 7 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive
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 8 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
-DLGLOBAL DEBUG 9 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup(),other
-DLGLOBAL DEBUG 8 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG 7 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 8 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 9 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup(),_bran
-DLGLOBAL DEBUG test(_branch0){alive}: EV_CHILD_GONE: Dropped reference _branch0.child[0] = __twig0a
-DLGLOBAL DEBUG test(_branch0){alive}: No more children
-DLGLOBAL DEBUG 8 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 7 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
+DLGLOBAL DEBUG test(other){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 6 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(__twig1a)
@@ -2169,36 +1607,17 @@ DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
DLGLOBAL DEBUG 7 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 8 (__twig1a.cleanup(),2*root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 9 (__twig1a.cleanup(),2*root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.
-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 8 (__twig1a.cleanup(),2*root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
-DLGLOBAL DEBUG 7 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
DLGLOBAL DEBUG 6 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 7 (__twig1a.cleanup(),2*root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.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 6 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: Deferring: will deallocate with test(__twig1a)
DLGLOBAL DEBUG 5 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup())
DLGLOBAL DEBUG test(other){alive}: cleanup() done
DLGLOBAL DEBUG 4 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: Deferring: will deallocate with test(__twig1a)
DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),_branch1.cleanup())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 4 (__twig1a.cleanup(),2*root.alive(),_branch1.cleanup())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 5 (__twig1a.cleanup(),2*root.alive(),_branch1.cleanup(),root.child_gone())
-DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
-DLGLOBAL DEBUG test(root){alive}: No more children
-DLGLOBAL DEBUG 4 (__twig1a.cleanup(),2*root.alive(),_branch1.cleanup())
-DLGLOBAL DEBUG test(root){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(_branch1){alive}: Deferring: will deallocate with test(__twig1a)
@@ -2209,22 +1628,10 @@ DLGLOBAL DEBUG test(root){alive}: cleanup() done
DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: Deferring: will deallocate with test(__twig1a)
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}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 1 (__twig1a.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1a){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(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1a){alive}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG --- after term cascade:
DLGLOBAL DEBUG --- all deallocated.
@@ -2288,14 +1695,7 @@ DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 4 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),__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.alive(),__twig1a.cleanup(),root.alive(),__twig1b.cleanup(),_branch1.alive())
-DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 6 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),__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.alive(),__twig1a.cleanup(),root.alive(),__twig1b.cleanup(),_branch1.alive())
-DLGLOBAL DEBUG 4 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),__twig1b.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 3 (__twig1a.alive(),__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(__twig1a)
@@ -2330,14 +1730,7 @@ DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 8 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.
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 9 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 10 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig
-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 9 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0
-DLGLOBAL DEBUG 8 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 7 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(__twig1a)
@@ -2348,21 +1741,8 @@ DLGLOBAL DEBUG 8 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.clea
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 9 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0
-DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
-DLGLOBAL DEBUG 10 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig
-DLGLOBAL DEBUG 9 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0
-DLGLOBAL DEBUG 8 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.
-DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 9 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 10 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig
-DLGLOBAL DEBUG test(_branch0){alive}: EV_CHILD_GONE: Dropped reference _branch0.child[0] = __twig0a
-DLGLOBAL DEBUG test(_branch0){alive}: No more children
-DLGLOBAL DEBUG 9 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0
-DLGLOBAL DEBUG test(_branch0){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 8 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.
+DLGLOBAL DEBUG test(other){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 7 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(__twig1a)
@@ -2370,36 +1750,17 @@ DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
DLGLOBAL DEBUG 8 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.
DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 9 (__twig1a.alive(),__twig1a.cleanup(),2*root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 10 (__twig1a.alive(),__twig1a.cleanup(),2*root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branc
-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 9 (__twig1a.alive(),__twig1a.cleanup(),2*root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch
-DLGLOBAL DEBUG 8 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
DLGLOBAL DEBUG 7 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 8 (__twig1a.alive(),__twig1a.cleanup(),2*root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.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 7 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: Deferring: will deallocate with test(__twig1a)
DLGLOBAL DEBUG 6 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup())
DLGLOBAL DEBUG test(other){alive}: cleanup() done
DLGLOBAL DEBUG 5 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: Deferring: will deallocate with test(__twig1a)
DLGLOBAL DEBUG 4 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 5 (__twig1a.alive(),__twig1a.cleanup(),2*root.alive(),_branch1.cleanup())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 6 (__twig1a.alive(),__twig1a.cleanup(),2*root.alive(),_branch1.cleanup(),root.child_gone())
-DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
-DLGLOBAL DEBUG test(root){alive}: No more children
-DLGLOBAL DEBUG 5 (__twig1a.alive(),__twig1a.cleanup(),2*root.alive(),_branch1.cleanup())
-DLGLOBAL DEBUG test(root){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 4 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
DLGLOBAL DEBUG 3 (__twig1a.alive(),__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(_branch1){alive}: Deferring: will deallocate with test(__twig1a)
@@ -2410,22 +1771,10 @@ DLGLOBAL DEBUG test(root){alive}: cleanup() done
DLGLOBAL DEBUG 3 (__twig1a.alive(),__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: Deferring: will deallocate with test(__twig1a)
DLGLOBAL DEBUG 2 (__twig1a.alive(),__twig1a.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 3 (__twig1a.alive(),__twig1a.cleanup(),_branch1.alive())
-DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 4 (__twig1a.alive(),__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 3 (__twig1a.alive(),__twig1a.cleanup(),_branch1.alive())
-DLGLOBAL DEBUG test(_branch1){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 2 (__twig1a.alive(),__twig1a.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 1 (__twig1a.alive())
-DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 2 (__twig1a.alive(),_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 1 (__twig1a.alive())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1a){alive}: Deallocated, including all deferred deallocations
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG --- after destroy-event cascade:
@@ -2506,23 +1855,8 @@ 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}: Ignoring trigger to terminate: already terminating
-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}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 1 (__twig1a.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(root)
@@ -2557,14 +1891,7 @@ DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup(),_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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(root)
@@ -2575,21 +1902,8 @@ DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.aliv
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 6 (_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup(),other.other_gone())
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup(),_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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
+DLGLOBAL DEBUG test(other){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(root)
@@ -2597,42 +1911,25 @@ DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),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 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.cleanup())
DLGLOBAL DEBUG test(other){alive}: cleanup() done
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (_branch1.cleanup())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 3 (_branch1.cleanup(),root.alive(),root.child_gone())
-DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
-DLGLOBAL DEBUG test(root){alive}: No more children
-DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 1 (_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(_branch1){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (root.cleanup())
DLGLOBAL DEBUG test(root){alive}: cleanup()
DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: removing reference root.other[0] -> __twig1a
+DLGLOBAL DEBUG test(__twig1a){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
DLGLOBAL DEBUG test(root){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(root){alive}: Deallocated, including all deferred deallocations
@@ -2716,23 +2013,8 @@ 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}: Ignoring trigger to terminate: already terminating
-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}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 1 (__twig1a.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(root)
@@ -2767,14 +2049,7 @@ DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup(),_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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(root)
@@ -2785,21 +2060,8 @@ DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.aliv
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 6 (_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup(),other.other_gone())
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup(),_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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
+DLGLOBAL DEBUG test(other){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(root)
@@ -2807,42 +2069,25 @@ DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 6 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 7 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_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 6 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),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 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch0){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.cleanup())
DLGLOBAL DEBUG test(other){alive}: cleanup() done
DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
DLGLOBAL DEBUG test(other){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (_branch1.cleanup())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 3 (_branch1.cleanup(),root.alive(),root.child_gone())
-DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
-DLGLOBAL DEBUG test(root){alive}: No more children
-DLGLOBAL DEBUG 2 (_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 1 (_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(_branch1){alive}: Deferring: will deallocate with test(root)
DLGLOBAL DEBUG 1 (root.cleanup())
DLGLOBAL DEBUG test(root){alive}: cleanup()
DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: removing reference root.other[0] -> __twig1a
+DLGLOBAL DEBUG test(__twig1a){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
DLGLOBAL DEBUG test(root){alive}: cleanup() done
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG test(root){alive}: Deallocated, including all deferred deallocations
@@ -2900,14 +2145,7 @@ DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 3 (other.cleanup(),_branch0.alive(),__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 4 (other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 5 (other.cleanup(),2*_branch0.alive(),__twig0b.cleanup(),_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 (other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG 3 (other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 2 (other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(other)
@@ -2918,21 +2156,8 @@ DLGLOBAL DEBUG 3 (other.cleanup(),_branch0.alive(),__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 (other.cleanup(),_branch0.alive(),__twig0a.cleanup(),other.alive())
-DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
-DLGLOBAL DEBUG 5 (other.cleanup(),_branch0.alive(),__twig0a.cleanup(),other.alive(),other.other_gone())
-DLGLOBAL DEBUG 4 (other.cleanup(),_branch0.alive(),__twig0a.cleanup(),other.alive())
-DLGLOBAL DEBUG 3 (other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 4 (other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 5 (other.cleanup(),2*_branch0.alive(),__twig0a.cleanup(),_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 4 (other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 3 (other.cleanup(),_branch0.alive(),__twig0a.cleanup())
+DLGLOBAL DEBUG test(other){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 2 (other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(other)
@@ -2972,14 +2197,7 @@ DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 3 (other.cleanup(),_branch1.alive(),__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 (other.cleanup(),2*_branch1.alive(),__twig1b.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 5 (other.cleanup(),2*_branch1.alive(),__twig1b.cleanup(),_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 (other.cleanup(),2*_branch1.alive(),__twig1b.cleanup())
-DLGLOBAL DEBUG 3 (other.cleanup(),_branch1.alive(),__twig1b.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 2 (other.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(other)
@@ -3007,15 +2225,7 @@ DLGLOBAL DEBUG test(root){alive}: cleanup() done
DLGLOBAL DEBUG 4 (other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: Deferring: will deallocate with test(other)
DLGLOBAL DEBUG 3 (other.cleanup(),_branch1.alive(),__twig1a.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 4 (other.cleanup(),2*_branch1.alive(),__twig1a.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 5 (other.cleanup(),2*_branch1.alive(),__twig1a.cleanup(),_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 (other.cleanup(),2*_branch1.alive(),__twig1a.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 3 (other.cleanup(),_branch1.alive(),__twig1a.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 2 (other.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(other)
@@ -3023,22 +2233,10 @@ DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
DLGLOBAL DEBUG 3 (other.cleanup(),_branch1.alive(),_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){alive}: cleanup()
DLGLOBAL DEBUG test(_branch1){alive}: scene forgets _branch1
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 4 (other.cleanup(),_branch1.alive(),_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 5 (other.cleanup(),_branch1.alive(),_branch1.cleanup(),root.alive(),root.child_gone())
-DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
-DLGLOBAL DEBUG test(root){alive}: No more children
-DLGLOBAL DEBUG 4 (other.cleanup(),_branch1.alive(),_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 3 (other.cleanup(),_branch1.alive(),_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
DLGLOBAL DEBUG 2 (other.cleanup(),_branch1.alive())
-DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 3 (other.cleanup(),_branch1.alive(),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 2 (other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: Deferring: will deallocate with test(other)
DLGLOBAL DEBUG 1 (other.cleanup())
DLGLOBAL DEBUG test(other){alive}: cleanup() done
@@ -3103,14 +2301,7 @@ DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch0.alive(),__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 5 (other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 6 (other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup(),_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 5 (other.alive(),other.cleanup(),2*_branch0.alive(),__twig0b.cleanup())
-DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
DLGLOBAL DEBUG 3 (other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0b){alive}: Deferring: will deallocate with test(other)
@@ -3121,21 +2312,8 @@ DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanu
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 (2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
-DLGLOBAL DEBUG 6 (2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup(),other.other_gone())
-DLGLOBAL DEBUG 5 (2*other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 5 (other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 6 (other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup(),_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 5 (other.alive(),other.cleanup(),2*_branch0.alive(),__twig0a.cleanup())
-DLGLOBAL DEBUG test(_branch0){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch0.alive(),__twig0a.cleanup())
+DLGLOBAL DEBUG test(other){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
DLGLOBAL DEBUG 3 (other.alive(),other.cleanup(),_branch0.alive())
DLGLOBAL DEBUG test(__twig0a){alive}: Deferring: will deallocate with test(other)
@@ -3175,14 +2353,7 @@ DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch1.alive(),__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 (other.alive(),other.cleanup(),2*_branch1.alive(),__twig1b.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 6 (other.alive(),other.cleanup(),2*_branch1.alive(),__twig1b.cleanup(),_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 (other.alive(),other.cleanup(),2*_branch1.alive(),__twig1b.cleanup())
-DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch1.alive(),__twig1b.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
DLGLOBAL DEBUG 3 (other.alive(),other.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(__twig1b){alive}: Deferring: will deallocate with test(other)
@@ -3210,15 +2381,7 @@ DLGLOBAL DEBUG test(root){alive}: cleanup() done
DLGLOBAL DEBUG 5 (other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: Deferring: will deallocate with test(other)
DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 5 (other.alive(),other.cleanup(),2*_branch1.alive(),__twig1a.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 6 (other.alive(),other.cleanup(),2*_branch1.alive(),__twig1a.cleanup(),_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 5 (other.alive(),other.cleanup(),2*_branch1.alive(),__twig1a.cleanup())
-DLGLOBAL DEBUG test(_branch1){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
DLGLOBAL DEBUG 3 (other.alive(),other.cleanup(),_branch1.alive())
DLGLOBAL DEBUG test(__twig1a){alive}: Deferring: will deallocate with test(other)
@@ -3226,27 +2389,2568 @@ DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch1.alive(),_branch1.cleanup())
DLGLOBAL DEBUG test(_branch1){alive}: cleanup()
DLGLOBAL DEBUG test(_branch1){alive}: scene forgets _branch1
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
+DLGLOBAL DEBUG 3 (other.alive(),other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: Deferring: will deallocate with test(other)
+DLGLOBAL DEBUG 2 (other.alive(),other.cleanup())
+DLGLOBAL DEBUG test(other){alive}: cleanup() done
+DLGLOBAL DEBUG 1 (other.alive())
+DLGLOBAL DEBUG test(other){alive}: Deallocated, including all deferred deallocations
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG --- after destroy-event cascade:
+DLGLOBAL DEBUG --- all deallocated.
+
+
+test_osmo_fsm_term_safely() done
+
+
+test_osmo_fsm_set_dealloc_ctx()
+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 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 (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+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}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(__twig1b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig1a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(__twig1a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 1 (_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 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}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(other){alive}: pre_term()
+DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),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 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
+DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch0){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig0a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch0){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.cleanup())
+DLGLOBAL DEBUG test(other){alive}: cleanup() done
+DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: Freeing instance
+DLGLOBAL DEBUG test(other){alive}: Deallocated
+DLGLOBAL DEBUG 1 (_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(_branch1){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch1){alive}: Deallocated
+DLGLOBAL DEBUG 1 (root.cleanup())
+DLGLOBAL DEBUG test(root){alive}: cleanup()
+DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: removing reference root.other[0] -> __twig1a
+DLGLOBAL DEBUG test(__twig1a){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(root){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(root){alive}: Freeing instance
+DLGLOBAL DEBUG test(root){alive}: Deallocated
+DLGLOBAL DEBUG --- after term cascade:
+DLGLOBAL DEBUG --- all deallocated.
+*** loop_ctx contains 33 blocks, deallocating.
+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 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}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(root){alive}: pre_term()
+DLGLOBAL DEBUG test(_branch1){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
+DLGLOBAL DEBUG 2 (root.alive(),__twig1b.cleanup())
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
+DLGLOBAL DEBUG 1 (root.alive())
+DLGLOBAL DEBUG test(__twig1b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig1a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
+DLGLOBAL DEBUG 2 (root.alive(),__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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
+DLGLOBAL DEBUG 1 (root.alive())
+DLGLOBAL DEBUG test(__twig1a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 2 (root.alive(),_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 3 (root.alive(),_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 4 (root.alive(),_branch1.cleanup(),other.alive(),other.other_gone())
+DLGLOBAL DEBUG test(other){alive}: EV_OTHER_GONE: Dropped reference other.other[1] = _branch1
+DLGLOBAL DEBUG 3 (root.alive(),_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(other){alive}: pre_term()
+DLGLOBAL DEBUG 4 (root.alive(),_branch1.cleanup(),other.alive(),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 5 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 6 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
+DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
+DLGLOBAL DEBUG 5 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch0){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 6 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
+DLGLOBAL DEBUG 5 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig0a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 6 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
+DLGLOBAL DEBUG 5 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 6 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
+DLGLOBAL DEBUG 5 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch0){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG 4 (root.alive(),_branch1.cleanup(),other.alive(),other.cleanup())
+DLGLOBAL DEBUG test(other){alive}: cleanup() done
+DLGLOBAL DEBUG 3 (root.alive(),_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: Freeing instance
+DLGLOBAL DEBUG test(other){alive}: Deallocated
+DLGLOBAL DEBUG 2 (root.alive(),_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
+DLGLOBAL DEBUG 1 (root.alive())
+DLGLOBAL DEBUG test(_branch1){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch1){alive}: Deallocated
+DLGLOBAL DEBUG 2 (root.alive(),root.cleanup())
+DLGLOBAL DEBUG test(root){alive}: cleanup()
+DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: removing reference root.other[0] -> __twig1a
+DLGLOBAL DEBUG test(__twig1a){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(root){alive}: cleanup() done
+DLGLOBAL DEBUG 1 (root.alive())
+DLGLOBAL DEBUG test(root){alive}: Freeing instance
+DLGLOBAL DEBUG test(root){alive}: Deallocated
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG --- after destroy-event cascade:
+DLGLOBAL DEBUG --- all deallocated.
+*** loop_ctx contains 33 blocks, deallocating.
+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 _branch0
+DLGLOBAL DEBUG test(_branch0){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch0){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+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}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(__twig0b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig0a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+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())
+DLGLOBAL DEBUG 1 (__twig0a.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(__twig0a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 1 (_branch0.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
+DLGLOBAL DEBUG test(_branch0){alive}: 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}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(other){alive}: pre_term()
+DLGLOBAL DEBUG 3 (_branch0.cleanup(),other.alive(),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[1] -> _branch1
+DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_OTHER_GONE
+DLGLOBAL DEBUG 4 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 5 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),_branch1.other_gone())
+DLGLOBAL DEBUG test(_branch1){alive}: EV_OTHER_GONE: Dropped reference _branch1.other[0] = other
+DLGLOBAL DEBUG 4 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(_branch1){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
+DLGLOBAL DEBUG 5 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1b.cleanup())
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(__twig1b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig1a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
+DLGLOBAL DEBUG 5 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__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 6 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 7 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive(),root.other_gone())
+DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
+DLGLOBAL DEBUG 6 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(root){alive}: pre_term()
+DLGLOBAL DEBUG test(_branch1){alive}: Ignoring trigger to terminate: already terminating
+DLGLOBAL ERROR test(root){alive}: Internal error while terminating child FSMs: a child FSM is stuck
+DLGLOBAL DEBUG 7 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive(),root.cleanup())
+DLGLOBAL DEBUG test(root){alive}: cleanup()
+DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: cleanup() done
+DLGLOBAL DEBUG 6 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: Freeing instance
+DLGLOBAL DEBUG test(root){alive}: Deallocated
+DLGLOBAL DEBUG 5 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(__twig1a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 5 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),_branch1.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch1){alive}: scene forgets _branch1
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(_branch1){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch1){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG 3 (_branch0.cleanup(),other.alive(),other.cleanup())
+DLGLOBAL DEBUG test(other){alive}: cleanup() done
+DLGLOBAL DEBUG 2 (_branch0.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: Freeing instance
+DLGLOBAL DEBUG test(other){alive}: Deallocated
+DLGLOBAL DEBUG 1 (_branch0.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(_branch0){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch0){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG --- after term cascade:
+DLGLOBAL DEBUG --- all deallocated.
+*** loop_ctx contains 33 blocks, deallocating.
+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 _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}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch0){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 2 (_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
+DLGLOBAL DEBUG 1 (_branch0.alive())
+DLGLOBAL DEBUG test(__twig0b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig0a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 2 (_branch0.alive(),__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 3 (_branch0.alive(),__twig0a.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 4 (_branch0.alive(),__twig0a.cleanup(),other.alive(),other.other_gone())
+DLGLOBAL DEBUG 3 (_branch0.alive(),__twig0a.cleanup(),other.alive())
+DLGLOBAL DEBUG 2 (_branch0.alive(),__twig0a.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
+DLGLOBAL DEBUG 1 (_branch0.alive())
+DLGLOBAL DEBUG test(__twig0a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 2 (_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
+DLGLOBAL DEBUG test(_branch0){alive}: removing reference _branch0.other[0] -> other
+DLGLOBAL DEBUG test(other){alive}: Received Event EV_OTHER_GONE
+DLGLOBAL DEBUG 3 (_branch0.alive(),_branch0.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 4 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.other_gone())
+DLGLOBAL DEBUG test(other){alive}: EV_OTHER_GONE: Dropped reference other.other[0] = _branch0
+DLGLOBAL DEBUG 3 (_branch0.alive(),_branch0.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(other){alive}: pre_term()
+DLGLOBAL DEBUG 4 (_branch0.alive(),_branch0.cleanup(),other.alive(),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[1] -> _branch1
+DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_OTHER_GONE
+DLGLOBAL DEBUG 5 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 6 (_branch0.alive(),_branch0.cleanup(),other.alive(),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.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(_branch1){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
+DLGLOBAL DEBUG 6 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1b.cleanup())
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
+DLGLOBAL DEBUG 5 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(__twig1b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig1a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
+DLGLOBAL DEBUG 6 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__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 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 8 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive(),root.othe
+DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
+DLGLOBAL DEBUG 7 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(root){alive}: pre_term()
+DLGLOBAL DEBUG test(_branch1){alive}: Ignoring trigger to terminate: already terminating
+DLGLOBAL ERROR test(root){alive}: Internal error while terminating child FSMs: a child FSM is stuck
+DLGLOBAL DEBUG 8 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive(),root.clea
+DLGLOBAL DEBUG test(root){alive}: cleanup()
+DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: cleanup() done
+DLGLOBAL DEBUG 7 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: Freeing instance
+DLGLOBAL DEBUG test(root){alive}: Deallocated
+DLGLOBAL DEBUG 6 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
+DLGLOBAL DEBUG 5 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(__twig1a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 6 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive(),_branch1.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch1){alive}: scene forgets _branch1
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
+DLGLOBAL DEBUG 5 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(_branch1){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch1){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG 4 (_branch0.alive(),_branch0.cleanup(),other.alive(),other.cleanup())
+DLGLOBAL DEBUG test(other){alive}: cleanup() done
+DLGLOBAL DEBUG 3 (_branch0.alive(),_branch0.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: Freeing instance
+DLGLOBAL DEBUG test(other){alive}: Deallocated
+DLGLOBAL DEBUG 2 (_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
+DLGLOBAL DEBUG 1 (_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch0){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG --- after destroy-event cascade:
+DLGLOBAL DEBUG --- all deallocated.
+*** loop_ctx contains 33 blocks, deallocating.
+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 __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())
+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(__twig0a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0a){alive}: Deallocated
+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 --- 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
+*** loop_ctx contains 5 blocks, deallocating.
+DLGLOBAL DEBUG test(root){alive}: Terminating (cause = OSMO_FSM_TERM_ERROR)
+DLGLOBAL DEBUG test(root){alive}: pre_term()
+DLGLOBAL DEBUG test(_branch1){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+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}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(__twig1b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig1a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(__twig1a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 1 (_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 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}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(other){alive}: pre_term()
+DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),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 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
+DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch0){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0b){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch0){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.cleanup())
+DLGLOBAL DEBUG test(other){alive}: cleanup() done
+DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: Freeing instance
+DLGLOBAL DEBUG test(other){alive}: Deallocated
+DLGLOBAL DEBUG 1 (_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(_branch1){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch1){alive}: Deallocated
+DLGLOBAL DEBUG 1 (root.cleanup())
+DLGLOBAL DEBUG test(root){alive}: cleanup()
+DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: removing reference root.other[0] -> __twig1a
+DLGLOBAL DEBUG test(__twig1a){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(root){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(root){alive}: Freeing instance
+DLGLOBAL DEBUG test(root){alive}: 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 __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}: 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 2 (__twig0a.alive(),__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 3 (__twig0a.alive(),__twig0a.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 4 (__twig0a.alive(),__twig0a.cleanup(),other.alive(),other.other_gone())
+DLGLOBAL DEBUG 3 (__twig0a.alive(),__twig0a.cleanup(),other.alive())
+DLGLOBAL DEBUG 2 (__twig0a.alive(),__twig0a.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
+DLGLOBAL DEBUG 3 (__twig0a.alive(),__twig0a.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
+DLGLOBAL DEBUG 4 (__twig0a.alive(),__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 3 (__twig0a.alive(),__twig0a.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG 2 (__twig0a.alive(),__twig0a.cleanup())
+DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
+DLGLOBAL DEBUG 1 (__twig0a.alive())
+DLGLOBAL DEBUG test(__twig0a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
+DLGLOBAL DEBUG 2 (__twig0a.alive(),_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 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
+*** loop_ctx contains 5 blocks, deallocating.
+DLGLOBAL DEBUG test(root){alive}: Terminating (cause = OSMO_FSM_TERM_ERROR)
+DLGLOBAL DEBUG test(root){alive}: pre_term()
+DLGLOBAL DEBUG test(_branch1){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+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}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(__twig1b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig1a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(__twig1a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 1 (_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 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}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(other){alive}: pre_term()
+DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),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 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
+DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch0){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0b){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch0){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.cleanup())
+DLGLOBAL DEBUG test(other){alive}: cleanup() done
+DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: Freeing instance
+DLGLOBAL DEBUG test(other){alive}: Deallocated
+DLGLOBAL DEBUG 1 (_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(_branch1){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch1){alive}: Deallocated
+DLGLOBAL DEBUG 1 (root.cleanup())
+DLGLOBAL DEBUG test(root){alive}: cleanup()
+DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: removing reference root.other[0] -> __twig1a
+DLGLOBAL DEBUG test(__twig1a){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(root){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(root){alive}: Freeing instance
+DLGLOBAL DEBUG test(root){alive}: 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 __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(__twig0b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0b){alive}: Deallocated
+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 --- 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
+*** loop_ctx contains 5 blocks, deallocating.
+DLGLOBAL DEBUG test(root){alive}: Terminating (cause = OSMO_FSM_TERM_ERROR)
+DLGLOBAL DEBUG test(root){alive}: pre_term()
+DLGLOBAL DEBUG test(_branch1){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+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}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(__twig1b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig1a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(__twig1a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 1 (_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 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}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(other){alive}: pre_term()
+DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),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 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
+DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch0){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch0){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.cleanup())
+DLGLOBAL DEBUG test(other){alive}: cleanup() done
+DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: Freeing instance
+DLGLOBAL DEBUG test(other){alive}: Deallocated
+DLGLOBAL DEBUG 1 (_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(_branch1){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch1){alive}: Deallocated
+DLGLOBAL DEBUG 1 (root.cleanup())
+DLGLOBAL DEBUG test(root){alive}: cleanup()
+DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: removing reference root.other[0] -> __twig1a
+DLGLOBAL DEBUG test(__twig1a){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(root){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(root){alive}: Freeing instance
+DLGLOBAL DEBUG test(root){alive}: 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 __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}: 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 2 (__twig0b.alive(),__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 3 (__twig0b.alive(),__twig0b.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_CHILD_GONE)
+DLGLOBAL DEBUG 4 (__twig0b.alive(),__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 3 (__twig0b.alive(),__twig0b.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG 2 (__twig0b.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
+DLGLOBAL DEBUG 1 (__twig0b.alive())
+DLGLOBAL DEBUG test(__twig0b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0b){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch0){alive}: Received Event EV_CHILD_GONE
+DLGLOBAL DEBUG 2 (__twig0b.alive(),_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 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
+*** loop_ctx contains 5 blocks, deallocating.
+DLGLOBAL DEBUG test(root){alive}: Terminating (cause = OSMO_FSM_TERM_ERROR)
+DLGLOBAL DEBUG test(root){alive}: pre_term()
+DLGLOBAL DEBUG test(_branch1){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+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}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(__twig1b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig1a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(__twig1a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 1 (_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 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}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(other){alive}: pre_term()
+DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),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 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
+DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch0){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch0){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.cleanup())
+DLGLOBAL DEBUG test(other){alive}: cleanup() done
+DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: Freeing instance
+DLGLOBAL DEBUG test(other){alive}: Deallocated
+DLGLOBAL DEBUG 1 (_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(_branch1){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch1){alive}: Deallocated
+DLGLOBAL DEBUG 1 (root.cleanup())
+DLGLOBAL DEBUG test(root){alive}: cleanup()
+DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: removing reference root.other[0] -> __twig1a
+DLGLOBAL DEBUG test(__twig1a){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(root){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(root){alive}: Freeing instance
+DLGLOBAL DEBUG test(root){alive}: 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 _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 (cause = OSMO_FSM_TERM_PARENT)
+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}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(__twig1b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig1a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+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}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(root){alive}: pre_term()
+DLGLOBAL DEBUG test(_branch1){alive}: Ignoring trigger to terminate: already terminating
+DLGLOBAL ERROR test(root){alive}: Internal error while terminating child FSMs: a child FSM is stuck
+DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.cleanup())
+DLGLOBAL DEBUG test(root){alive}: cleanup()
+DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: cleanup() done
+DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: Freeing instance
+DLGLOBAL DEBUG test(root){alive}: Deallocated
+DLGLOBAL DEBUG 1 (__twig1a.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(__twig1a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 1 (_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 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}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(other){alive}: pre_term()
+DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),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 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
+DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch0){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig0a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch0){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.cleanup())
+DLGLOBAL DEBUG test(other){alive}: cleanup() done
+DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: Freeing instance
+DLGLOBAL DEBUG test(other){alive}: Deallocated
+DLGLOBAL DEBUG 1 (_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(_branch1){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch1){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG --- after term cascade:
+DLGLOBAL DEBUG --- all deallocated.
+*** loop_ctx contains 33 blocks, deallocating.
+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}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
+DLGLOBAL DEBUG 2 (_branch1.alive(),__twig1b.cleanup())
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
+DLGLOBAL DEBUG 1 (_branch1.alive())
+DLGLOBAL DEBUG test(__twig1b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig1a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
+DLGLOBAL DEBUG 2 (_branch1.alive(),__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 3 (_branch1.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 4 (_branch1.alive(),__twig1a.cleanup(),root.alive(),root.other_gone())
+DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
+DLGLOBAL DEBUG 3 (_branch1.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(root){alive}: pre_term()
+DLGLOBAL DEBUG test(_branch1){alive}: Ignoring trigger to terminate: already terminating
+DLGLOBAL ERROR test(root){alive}: Internal error while terminating child FSMs: a child FSM is stuck
+DLGLOBAL DEBUG 4 (_branch1.alive(),__twig1a.cleanup(),root.alive(),root.cleanup())
+DLGLOBAL DEBUG test(root){alive}: cleanup()
+DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: cleanup() done
+DLGLOBAL DEBUG 3 (_branch1.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: Freeing instance
+DLGLOBAL DEBUG test(root){alive}: Deallocated
+DLGLOBAL DEBUG 2 (_branch1.alive(),__twig1a.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
+DLGLOBAL DEBUG 1 (_branch1.alive())
+DLGLOBAL DEBUG test(__twig1a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 2 (_branch1.alive(),_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 3 (_branch1.alive(),_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 4 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.other_gone())
+DLGLOBAL DEBUG test(other){alive}: EV_OTHER_GONE: Dropped reference other.other[1] = _branch1
+DLGLOBAL DEBUG 3 (_branch1.alive(),_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(other){alive}: pre_term()
+DLGLOBAL DEBUG 4 (_branch1.alive(),_branch1.cleanup(),other.alive(),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 5 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 6 (_branch1.alive(),_branch1.cleanup(),other.alive(),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.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch0){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 6 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
+DLGLOBAL DEBUG 5 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig0a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 6 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
+DLGLOBAL DEBUG 5 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 6 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
+DLGLOBAL DEBUG 5 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch0){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG 4 (_branch1.alive(),_branch1.cleanup(),other.alive(),other.cleanup())
+DLGLOBAL DEBUG test(other){alive}: cleanup() done
+DLGLOBAL DEBUG 3 (_branch1.alive(),_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: Freeing instance
+DLGLOBAL DEBUG test(other){alive}: Deallocated
+DLGLOBAL DEBUG 2 (_branch1.alive(),_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
+DLGLOBAL DEBUG 1 (_branch1.alive())
+DLGLOBAL DEBUG test(_branch1){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch1){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG --- after destroy-event cascade:
+DLGLOBAL DEBUG --- all deallocated.
+*** loop_ctx contains 33 blocks, deallocating.
+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}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(root){alive}: pre_term()
+DLGLOBAL DEBUG test(_branch1){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
+DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),__twig1b.cleanup())
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
+DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(__twig1b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1b){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),_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 4 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 5 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.other_gone())
+DLGLOBAL DEBUG test(other){alive}: EV_OTHER_GONE: Dropped reference other.other[1] = _branch1
+DLGLOBAL DEBUG 4 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(other){alive}: pre_term()
+DLGLOBAL DEBUG 5 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),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 6 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 7 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
+DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
+DLGLOBAL DEBUG 6 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch0){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 7 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
+DLGLOBAL DEBUG 6 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig0a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 7 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
+DLGLOBAL DEBUG 6 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 7 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
+DLGLOBAL DEBUG 6 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch0){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG 5 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup())
+DLGLOBAL DEBUG test(other){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: Freeing instance
+DLGLOBAL DEBUG test(other){alive}: Deallocated
+DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
+DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(_branch1){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch1){alive}: Deallocated
+DLGLOBAL DEBUG 3 (__twig1a.cleanup(),root.alive(),root.cleanup())
+DLGLOBAL DEBUG test(root){alive}: cleanup()
+DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: cleanup() done
+DLGLOBAL DEBUG 2 (__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: Freeing instance
+DLGLOBAL DEBUG test(root){alive}: Deallocated
+DLGLOBAL DEBUG 1 (__twig1a.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(__twig1a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG --- after term cascade:
+DLGLOBAL DEBUG --- all deallocated.
+*** loop_ctx contains 33 blocks, deallocating.
+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}: 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 2 (__twig1a.alive(),__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 3 (__twig1a.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 4 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),root.other_gone())
+DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
+DLGLOBAL DEBUG 3 (__twig1a.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(root){alive}: pre_term()
+DLGLOBAL DEBUG test(_branch1){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
+DLGLOBAL DEBUG 4 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),__twig1b.cleanup())
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
+DLGLOBAL DEBUG 3 (__twig1a.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(__twig1b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1b){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 4 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_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.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 6 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_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.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(other){alive}: pre_term()
+DLGLOBAL DEBUG 6 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),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 7 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 8 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.
+DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
+DLGLOBAL DEBUG 7 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch0){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 8 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
+DLGLOBAL DEBUG 7 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig0a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 8 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0a.
+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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
+DLGLOBAL DEBUG 7 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 8 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
+DLGLOBAL DEBUG 7 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch0){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG 6 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive(),other.cleanup())
+DLGLOBAL DEBUG test(other){alive}: cleanup() done
+DLGLOBAL DEBUG 5 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: Freeing instance
+DLGLOBAL DEBUG test(other){alive}: Deallocated
+DLGLOBAL DEBUG 4 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
+DLGLOBAL DEBUG 3 (__twig1a.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(_branch1){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch1){alive}: Deallocated
+DLGLOBAL DEBUG 4 (__twig1a.alive(),__twig1a.cleanup(),root.alive(),root.cleanup())
+DLGLOBAL DEBUG test(root){alive}: cleanup()
+DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: cleanup() done
+DLGLOBAL DEBUG 3 (__twig1a.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: Freeing instance
+DLGLOBAL DEBUG test(root){alive}: Deallocated
+DLGLOBAL DEBUG 2 (__twig1a.alive(),__twig1a.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
+DLGLOBAL DEBUG 1 (__twig1a.alive())
+DLGLOBAL DEBUG test(__twig1a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG --- after destroy-event cascade:
+DLGLOBAL DEBUG --- all deallocated.
+*** loop_ctx contains 33 blocks, deallocating.
+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(__twig1b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1b){alive}: Deallocated
+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 --- 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
+*** loop_ctx contains 5 blocks, deallocating.
+DLGLOBAL DEBUG test(root){alive}: Terminating (cause = OSMO_FSM_TERM_ERROR)
+DLGLOBAL DEBUG test(root){alive}: pre_term()
+DLGLOBAL DEBUG test(_branch1){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(__twig1a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 1 (_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 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}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(other){alive}: pre_term()
+DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),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 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
+DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch0){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig0a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch0){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.cleanup())
+DLGLOBAL DEBUG test(other){alive}: cleanup() done
+DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: Freeing instance
+DLGLOBAL DEBUG test(other){alive}: Deallocated
+DLGLOBAL DEBUG 1 (_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(_branch1){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch1){alive}: Deallocated
+DLGLOBAL DEBUG 1 (root.cleanup())
+DLGLOBAL DEBUG test(root){alive}: cleanup()
+DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: removing reference root.other[0] -> __twig1a
+DLGLOBAL DEBUG test(__twig1a){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(root){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(root){alive}: Freeing instance
+DLGLOBAL DEBUG test(root){alive}: 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 __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}: 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 2 (__twig1b.alive(),__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 3 (__twig1b.alive(),__twig1b.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_CHILD_GONE)
+DLGLOBAL DEBUG 4 (__twig1b.alive(),__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 3 (__twig1b.alive(),__twig1b.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG 2 (__twig1b.alive(),__twig1b.cleanup())
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
+DLGLOBAL DEBUG 1 (__twig1b.alive())
+DLGLOBAL DEBUG test(__twig1b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1b){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch1){alive}: Received Event EV_CHILD_GONE
+DLGLOBAL DEBUG 2 (__twig1b.alive(),_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 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
+*** loop_ctx contains 5 blocks, deallocating.
+DLGLOBAL DEBUG test(root){alive}: Terminating (cause = OSMO_FSM_TERM_ERROR)
+DLGLOBAL DEBUG test(root){alive}: pre_term()
+DLGLOBAL DEBUG test(_branch1){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(__twig1a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 1 (_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 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}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(other){alive}: pre_term()
+DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),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 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
+DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch0){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig0a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),__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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 5 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (_branch1.cleanup(),other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch0){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG 3 (_branch1.cleanup(),other.alive(),other.cleanup())
+DLGLOBAL DEBUG test(other){alive}: cleanup() done
+DLGLOBAL DEBUG 2 (_branch1.cleanup(),other.alive())
+DLGLOBAL DEBUG test(other){alive}: Freeing instance
+DLGLOBAL DEBUG test(other){alive}: Deallocated
+DLGLOBAL DEBUG 1 (_branch1.cleanup())
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(_branch1){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch1){alive}: Deallocated
+DLGLOBAL DEBUG 1 (root.cleanup())
+DLGLOBAL DEBUG test(root){alive}: cleanup()
+DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: removing reference root.other[0] -> __twig1a
+DLGLOBAL DEBUG test(__twig1a){alive}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(root){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(root){alive}: Freeing instance
+DLGLOBAL DEBUG test(root){alive}: 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 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}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch0){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 3 (other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
+DLGLOBAL DEBUG 2 (other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig0a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 3 (other.cleanup(),_branch0.alive(),__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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
+DLGLOBAL DEBUG 2 (other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 3 (other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 5 (other.alive(),other.cleanup(),_branch1.alive(),_branch1.cleanup(),root.alive())
+DLGLOBAL DEBUG 4 (other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
-DLGLOBAL DEBUG 6 (other.alive(),other.cleanup(),_branch1.alive(),_branch1.cleanup(),root.alive(),root.child_gone())
-DLGLOBAL DEBUG test(root){alive}: EV_CHILD_GONE: Dropped reference root.child[1] = _branch1
-DLGLOBAL DEBUG test(root){alive}: No more children
-DLGLOBAL DEBUG 5 (other.alive(),other.cleanup(),_branch1.alive(),_branch1.cleanup(),root.alive())
-DLGLOBAL DEBUG test(root){alive}: Ignoring trigger to terminate: already terminating
-DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch1.alive(),_branch1.cleanup())
+DLGLOBAL DEBUG 5 (other.cleanup(),_branch0.alive(),_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 4 (other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
+DLGLOBAL DEBUG 3 (other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
+DLGLOBAL DEBUG 2 (other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch0){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
+DLGLOBAL DEBUG 3 (other.cleanup(),_branch0.alive(),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 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}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
+DLGLOBAL DEBUG 3 (other.cleanup(),_branch1.alive(),__twig1b.cleanup())
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
+DLGLOBAL DEBUG 2 (other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(__twig1b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig1a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
+DLGLOBAL DEBUG 3 (other.cleanup(),_branch1.alive(),__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 (other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 5 (other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive(),root.other_gone())
+DLGLOBAL DEBUG test(root){alive}: EV_OTHER_GONE: Dropped reference root.other[0] = __twig1a
+DLGLOBAL DEBUG 4 (other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(root){alive}: pre_term()
+DLGLOBAL DEBUG test(_branch1){alive}: Ignoring trigger to terminate: already terminating
+DLGLOBAL ERROR test(root){alive}: Internal error while terminating child FSMs: a child FSM is stuck
+DLGLOBAL DEBUG 5 (other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive(),root.cleanup())
+DLGLOBAL DEBUG test(root){alive}: cleanup()
+DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: cleanup() done
+DLGLOBAL DEBUG 4 (other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: Freeing instance
+DLGLOBAL DEBUG test(root){alive}: Deallocated
+DLGLOBAL DEBUG 3 (other.cleanup(),_branch1.alive(),__twig1a.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
+DLGLOBAL DEBUG 2 (other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(__twig1a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 3 (other.cleanup(),_branch1.alive(),_branch1.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch1){alive}: scene forgets _branch1
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
-DLGLOBAL DEBUG 3 (other.alive(),other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG 2 (other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(_branch1){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch1){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG 1 (other.cleanup())
+DLGLOBAL DEBUG test(other){alive}: cleanup() done
+DLGLOBAL DEBUG 0 (-)
+DLGLOBAL DEBUG test(other){alive}: Freeing instance
+DLGLOBAL DEBUG test(other){alive}: Deallocated
+DLGLOBAL DEBUG --- after term cascade:
+DLGLOBAL DEBUG --- all deallocated.
+*** loop_ctx contains 33 blocks, deallocating.
+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}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(other){alive}: pre_term()
+DLGLOBAL DEBUG 2 (other.alive(),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 3 (other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch0.alive(),_branch0.other_gone())
+DLGLOBAL DEBUG test(_branch0){alive}: EV_OTHER_GONE: Dropped reference _branch0.other[0] = other
+DLGLOBAL DEBUG 3 (other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch0){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0b){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch0.alive(),__twig0b.cleanup())
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig0b){alive}: scene forgets __twig0b
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0b){alive}: cleanup() done
+DLGLOBAL DEBUG 3 (other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig0a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig0a){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig0a){alive}: Removing from parent test(_branch0)
+DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch0.alive(),__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}: FSM instance already terminating, not dispatching event EV_OTHER_GONE
+DLGLOBAL DEBUG test(_branch0){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig0a){alive}: cleanup() done
+DLGLOBAL DEBUG 3 (other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(__twig0a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig0a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch0){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch0){alive}: scene forgets _branch0
DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
-DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch1.alive(),root.alive())
+DLGLOBAL DEBUG 5 (other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: alive(EV_CHILD_GONE)
+DLGLOBAL DEBUG 6 (other.alive(),other.cleanup(),_branch0.alive(),_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.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup(),root.alive())
+DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch0.alive(),_branch0.cleanup())
+DLGLOBAL DEBUG test(_branch0){alive}: cleanup() done
+DLGLOBAL DEBUG 3 (other.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG test(_branch0){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch0){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: Received Event EV_CHILD_GONE
+DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch0.alive(),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.alive(),other.cleanup(),_branch0.alive())
+DLGLOBAL DEBUG 2 (other.alive(),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 3 (other.alive(),other.cleanup(),_branch1.alive())
-DLGLOBAL DEBUG test(_branch1){alive}: Deferring: will deallocate with test(other)
+DLGLOBAL DEBUG test(_branch1){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch1.alive(),_branch1.other_gone())
+DLGLOBAL DEBUG test(_branch1){alive}: EV_OTHER_GONE: Dropped reference _branch1.other[0] = other
+DLGLOBAL DEBUG 3 (other.alive(),other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(_branch1){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(_branch1){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig1b){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1b){alive}: Removing from parent test(_branch1)
+DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch1.alive(),__twig1b.cleanup())
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup()
+DLGLOBAL DEBUG test(__twig1b){alive}: scene forgets __twig1b
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1b){alive}: cleanup() done
+DLGLOBAL DEBUG 3 (other.alive(),other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(__twig1b){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1b){alive}: Deallocated
+DLGLOBAL DEBUG test(__twig1a){alive}: Terminating (cause = OSMO_FSM_TERM_PARENT)
+DLGLOBAL DEBUG test(__twig1a){alive}: pre_term()
+DLGLOBAL DEBUG test(__twig1a){alive}: Removing from parent test(_branch1)
+DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch1.alive(),__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.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: alive(EV_OTHER_GONE)
+DLGLOBAL DEBUG 6 (other.alive(),other.cleanup(),_branch1.alive(),__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.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DLGLOBAL DEBUG test(root){alive}: pre_term()
+DLGLOBAL DEBUG test(_branch1){alive}: Ignoring trigger to terminate: already terminating
+DLGLOBAL ERROR test(root){alive}: Internal error while terminating child FSMs: a child FSM is stuck
+DLGLOBAL DEBUG 6 (other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive(),root.cleanup())
+DLGLOBAL DEBUG test(root){alive}: cleanup()
+DLGLOBAL DEBUG test(root){alive}: scene forgets root
+DLGLOBAL DEBUG test(root){alive}: cleanup() done
+DLGLOBAL DEBUG 5 (other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup(),root.alive())
+DLGLOBAL DEBUG test(root){alive}: Freeing instance
+DLGLOBAL DEBUG test(root){alive}: Deallocated
+DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch1.alive(),__twig1a.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(__twig1a){alive}: cleanup() done
+DLGLOBAL DEBUG 3 (other.alive(),other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(__twig1a){alive}: Freeing instance
+DLGLOBAL DEBUG test(__twig1a){alive}: Deallocated
+DLGLOBAL DEBUG test(_branch1){alive}: Removing from parent test(root)
+DLGLOBAL DEBUG 4 (other.alive(),other.cleanup(),_branch1.alive(),_branch1.cleanup())
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup()
+DLGLOBAL DEBUG test(_branch1){alive}: scene forgets _branch1
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
+DLGLOBAL DEBUG test(_branch1){alive}: cleanup() done
+DLGLOBAL DEBUG 3 (other.alive(),other.cleanup(),_branch1.alive())
+DLGLOBAL DEBUG test(_branch1){alive}: Freeing instance
+DLGLOBAL DEBUG test(_branch1){alive}: Deallocated
+DLGLOBAL DEBUG test(root){alive}: FSM instance already terminating, not dispatching event EV_CHILD_GONE
DLGLOBAL DEBUG 2 (other.alive(),other.cleanup())
DLGLOBAL DEBUG test(other){alive}: cleanup() done
DLGLOBAL DEBUG 1 (other.alive())
-DLGLOBAL DEBUG test(other){alive}: Deallocated, including all deferred deallocations
+DLGLOBAL DEBUG test(other){alive}: Freeing instance
+DLGLOBAL DEBUG test(other){alive}: Deallocated
DLGLOBAL DEBUG 0 (-)
DLGLOBAL DEBUG --- after destroy-event cascade:
DLGLOBAL DEBUG --- all deallocated.
+*** loop_ctx contains 33 blocks, deallocating.
+
+
+test_osmo_fsm_set_dealloc_ctx() done
diff --git a/tests/gsm0408/gsm0408_test.c b/tests/gsm0408/gsm0408_test.c
index db1d45ad..96178237 100644
--- a/tests/gsm0408/gsm0408_test.c
+++ b/tests/gsm0408/gsm0408_test.c
@@ -26,6 +26,7 @@
#include <osmocom/gsm/gsm48_ie.h>
#include <osmocom/gsm/gsm48.h>
#include <osmocom/gsm/gsm48_arfcn_range_encode.h>
+#include <osmocom/gsm/gsm_utils.h>
#include <osmocom/gsm/mncc.h>
#include <osmocom/core/backtrace.h>
#include <osmocom/core/utils.h>
@@ -1132,6 +1133,48 @@ static void test_si_range_helpers()
VERIFY(f0, ==, 1);
}
+static void test_power_ctrl()
+{
+ int8_t rc8;
+ int rc;
+
+ rc8 = osmo_gsm48_rfpowercap2powerclass(GSM_BAND_850, 0x00);
+ VERIFY(rc8, ==, 1);
+ rc8 = osmo_gsm48_rfpowercap2powerclass(GSM_BAND_900, 0x02);
+ VERIFY(rc8, ==, 3);
+ rc8 = osmo_gsm48_rfpowercap2powerclass(GSM_BAND_1800, 0x02);
+ VERIFY(rc8, ==, 3);
+ rc8 = osmo_gsm48_rfpowercap2powerclass(GSM_BAND_1900, 0x02);
+ VERIFY(rc8, ==, 3);
+ rc8 = osmo_gsm48_rfpowercap2powerclass(GSM_BAND_1900, 0x04);
+ VERIFY(rc8, <, 0);
+ rc8 = osmo_gsm48_rfpowercap2powerclass(GSM_BAND_900, 0x04);
+ VERIFY(rc8, ==, 5);
+ rc8 = osmo_gsm48_rfpowercap2powerclass(GSM_BAND_900, 0x05);
+ VERIFY(rc8, <, 0);
+ rc8 = osmo_gsm48_rfpowercap2powerclass(GSM_BAND_900, 0xf2);
+ VERIFY(rc8, <, 0);
+
+ rc = ms_class_gmsk_dbm(GSM_BAND_850, 0);
+ VERIFY(rc, <, 0);
+ rc = ms_class_gmsk_dbm(GSM_BAND_850, 1);
+ VERIFY(rc, ==, 43);
+ rc = ms_class_gmsk_dbm(GSM_BAND_900, 3);
+ VERIFY(rc, ==, 37);
+ rc = ms_class_gmsk_dbm(GSM_BAND_1800, 2);
+ VERIFY(rc, ==, 24);
+ rc = ms_class_gmsk_dbm(GSM_BAND_1800, 3);
+ VERIFY(rc, ==, 36);
+ rc = ms_class_gmsk_dbm(GSM_BAND_1900, 3);
+ VERIFY(rc, ==, 33);
+ rc = ms_class_gmsk_dbm(GSM_BAND_1900, 4);
+ VERIFY(rc, <, 0);
+ rc = ms_class_gmsk_dbm(GSM_BAND_900, 5);
+ VERIFY(rc, ==, 29);
+ rc = ms_class_gmsk_dbm(GSM_BAND_900, 6);
+ VERIFY(rc, <, 0);
+}
+
int main(int argc, char **argv)
{
test_bearer_cap();
@@ -1147,6 +1190,7 @@ int main(int argc, char **argv)
test_arfcn_filter();
test_print_encoding();
test_range_encoding();
+ test_power_ctrl();
return EXIT_SUCCESS;
}
diff --git a/tests/gsm0502/gsm0502_test.c b/tests/gsm0502/gsm0502_test.c
new file mode 100644
index 00000000..a950c6c1
--- /dev/null
+++ b/tests/gsm0502/gsm0502_test.c
@@ -0,0 +1,159 @@
+/*
+ * (C) 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Philipp Maier <pmaier@sysmocom.de>
+ * All Rights Reserved
+ *
+ * SPDX-License-Identifier: GPL-2.0+
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/gsm/gsm0502.h>
+
+/* TCH-F, block endings, 3x 104-frame cycles */
+uint32_t tch_f_fn_samples[] = { 1036987, 1036991, 1036995, 1037000, 1037004, 1037008, 1037013, 1037017,
+ 1037021, 1037026, 1037030, 1037034, 1037039, 1037043, 1037047, 1037052,
+ 1037056, 1037060, 1037065,
+ 1037069, 1037073, 1037078, 1037082, 1037086, 1037091, 1037095, 1037099,
+ 1037104, 1037108, 1037112,
+ 1037117, 1037121, 1037125, 1037130, 1037134, 1037138, 1037143, 1037147,
+ 1037151, 1037156, 1037160,
+ 1037164, 1037169, 1037173, 1037177, 1037182, 1037186, 1037190, 1037195,
+ 1037199, 1037203, 1037208,
+ 1037212, 1037216, 1037221, 1037225, 1037229, 1037234, 1037238, 1037242,
+ 1037247, 1037251, 1037255,
+ 1037260, 1037264, 1037268, 1037273, 1037277, 1037281, 1037286, 1037290,
+ 1037294
+};
+
+/* TCH-H0, block endings, 3x 104-frame cycles */
+uint32_t tch_h0_fn_samples[] = { 1175826, 1175830, 1175834, 1175839, 1175843, 1175847, 1175852, 1175856,
+ 1175860, 1175865, 1175869, 1175873, 1175878, 1175882, 1175886, 1175891,
+ 1175895, 1175899, 1175904,
+ 1175908, 1175912, 1175917, 1175921, 1175925, 1175930, 1175934, 1175938,
+ 1175943, 1175947, 1175951,
+ 1175956, 1175960, 1175964, 1175969, 1175973, 1175977, 1175982, 1175986,
+ 1175990, 1175995, 1175999,
+ 1176003, 1176008, 1176012, 1176016, 1176021, 1176025, 1176029, 1176034,
+ 1176038, 1176042, 1176047,
+ 1176051, 1176055, 1176060, 1176064, 1176068, 1176073, 1176077, 1176081,
+ 1176086, 1176090, 1176094,
+ 1176099, 1176103, 1176107, 1176112, 1176116, 1176120, 1176125, 1176129,
+ 1176133
+};
+
+/* TCH-H1, block endings, 3x 104-frame cycles */
+unsigned int tch_h1_fn_samples[] = { 1175827, 1175831, 1175835, 1175840, 1175844, 1175848, 1175853, 1175857,
+ 1175861, 1175866, 1175870, 1175874, 1175879, 1175883, 1175887, 1175892,
+ 1175896, 1175900, 1175905,
+ 1175909, 1175913, 1175918, 1175922, 1175926, 1175931, 1175935, 1175939,
+ 1175944, 1175948, 1175952,
+ 1175957, 1175961, 1175965, 1175970, 1175974, 1175978, 1175983, 1175987,
+ 1175991, 1175996, 1176000,
+ 1176004, 1176009, 1176013, 1176017, 1176022, 1176026, 1176030, 1176035,
+ 1176039, 1176043, 1176048,
+ 1176052, 1176056, 1176061, 1176065, 1176069, 1176074, 1176078, 1176082,
+ 1176087, 1176091, 1176095,
+ 1176100, 1176104, 1176108, 1176113, 1176117, 1176121, 1176126, 1176130,
+ 1176134
+};
+
+/* FACCH-F, block endings */
+uint32_t facch_f_fn_samples[] = { 177275, 177314, 177336, 177375, 177396, 177435, 178328, 178367, 178393,
+ 180014, 180053, 180079, 180113, 180144, 180183
+};
+
+/* FACCH-H0, block endings */
+uint32_t facch_h0_fn_samples[] = { 499956, 499999, 500034, 500077, 500952, 501836, 501880, 502850, 502894,
+ 502937, 503006, 503050
+};
+
+/* FACCH-H1, block endings */
+uint32_t facch_h1_fn_samples[] = { 500728, 500771, 500797, 500841, 500875, 500919, 501751, 501794, 501837,
+ 502782, 502825, 502869, 502903, 502955, 502999
+};
+
+static void test_gsm0502_fn_remap()
+{
+ unsigned int i;
+ uint32_t fn_begin;
+ uint32_t fn_end;
+
+ printf("Testing gsm0502_fn_remap()\n");
+ printf("TCH/F\n");
+ for (i = 0; i < ARRAY_SIZE(tch_h1_fn_samples); i++) {
+ fn_end = tch_f_fn_samples[i];
+ fn_begin = gsm0502_fn_remap(fn_end, FN_REMAP_TCH_F);
+ printf("fn_end=%u, fn_end%%104=%u, fn_begin=%u, fn_begin%%104=%u\n", fn_end, fn_end % 104, fn_begin,
+ fn_begin % 104);
+ }
+ printf("\n");
+
+ printf("TCH/H0\n");
+ for (i = 0; i < ARRAY_SIZE(tch_h0_fn_samples); i++) {
+ fn_end = tch_h0_fn_samples[i];
+ fn_begin = gsm0502_fn_remap(fn_end, FN_REMAP_TCH_H0);
+ printf("fn_end=%u, fn_end%%104=%u, fn_begin=%u, fn_begin%%104=%u\n", fn_end, fn_end % 104, fn_begin,
+ fn_begin % 104);
+ }
+ printf("\n");
+
+ printf("TCH/H1\n");
+ for (i = 0; i < ARRAY_SIZE(tch_h1_fn_samples); i++) {
+ fn_end = tch_h1_fn_samples[i];
+ fn_begin = gsm0502_fn_remap(fn_end, FN_REMAP_TCH_H1);
+ printf("fn_end=%u, fn_end%%104=%u, fn_begin=%u, fn_begin%%104=%u\n", fn_end, fn_end % 104, fn_begin,
+ fn_begin % 104);
+ }
+ printf("\n");
+
+ printf("FACCH/F\n");
+ for (i = 0; i < ARRAY_SIZE(facch_f_fn_samples); i++) {
+ fn_end = facch_f_fn_samples[i];
+ fn_begin = gsm0502_fn_remap(fn_end, FN_REMAP_FACCH_F);
+ printf("fn_end=%u, fn_end%%104=%u, fn_begin=%u, fn_begin%%104=%u\n", fn_end, fn_end % 104, fn_begin,
+ fn_begin % 104);
+ }
+ printf("\n");
+
+ printf("FACCH/H0\n");
+ for (i = 0; i < ARRAY_SIZE(facch_h0_fn_samples); i++) {
+ fn_end = facch_h0_fn_samples[i];
+ fn_begin = gsm0502_fn_remap(fn_end, FN_REMAP_FACCH_H0);
+ printf("fn_end=%u, fn_end%%104=%u, fn_begin=%u, fn_begin%%104=%u\n", fn_end, fn_end % 104, fn_begin,
+ fn_begin % 104);
+ }
+ printf("\n");
+
+ printf("FACCH/H1\n");
+ for (i = 0; i < ARRAY_SIZE(facch_h1_fn_samples); i++) {
+ fn_end = facch_h1_fn_samples[i];
+ fn_begin = gsm0502_fn_remap(fn_end, FN_REMAP_FACCH_H1);
+ printf("fn_end=%u, fn_end%%104=%u, fn_begin=%u, fn_begin%%104=%u\n", fn_end, fn_end % 104, fn_begin,
+ fn_begin % 104);
+ }
+ printf("\n");
+}
+
+int main(int argc, char **argv)
+{
+ test_gsm0502_fn_remap();
+ return EXIT_SUCCESS;
+}
diff --git a/tests/gsm0502/gsm0502_test.ok b/tests/gsm0502/gsm0502_test.ok
new file mode 100644
index 00000000..89418d3f
--- /dev/null
+++ b/tests/gsm0502/gsm0502_test.ok
@@ -0,0 +1,271 @@
+Testing gsm0502_fn_remap()
+TCH/F
+fn_end=1036987, fn_end%104=3, fn_begin=1036979, fn_begin%104=99
+fn_end=1036991, fn_end%104=7, fn_begin=1036984, fn_begin%104=0
+fn_end=1036995, fn_end%104=11, fn_begin=1036988, fn_begin%104=4
+fn_end=1037000, fn_end%104=16, fn_begin=1036992, fn_begin%104=8
+fn_end=1037004, fn_end%104=20, fn_begin=1036997, fn_begin%104=13
+fn_end=1037008, fn_end%104=24, fn_begin=1037001, fn_begin%104=17
+fn_end=1037013, fn_end%104=29, fn_begin=1037005, fn_begin%104=21
+fn_end=1037017, fn_end%104=33, fn_begin=1037010, fn_begin%104=26
+fn_end=1037021, fn_end%104=37, fn_begin=1037014, fn_begin%104=30
+fn_end=1037026, fn_end%104=42, fn_begin=1037018, fn_begin%104=34
+fn_end=1037030, fn_end%104=46, fn_begin=1037023, fn_begin%104=39
+fn_end=1037034, fn_end%104=50, fn_begin=1037027, fn_begin%104=43
+fn_end=1037039, fn_end%104=55, fn_begin=1037031, fn_begin%104=47
+fn_end=1037043, fn_end%104=59, fn_begin=1037036, fn_begin%104=52
+fn_end=1037047, fn_end%104=63, fn_begin=1037040, fn_begin%104=56
+fn_end=1037052, fn_end%104=68, fn_begin=1037044, fn_begin%104=60
+fn_end=1037056, fn_end%104=72, fn_begin=1037049, fn_begin%104=65
+fn_end=1037060, fn_end%104=76, fn_begin=1037053, fn_begin%104=69
+fn_end=1037065, fn_end%104=81, fn_begin=1037057, fn_begin%104=73
+fn_end=1037069, fn_end%104=85, fn_begin=1037062, fn_begin%104=78
+fn_end=1037073, fn_end%104=89, fn_begin=1037066, fn_begin%104=82
+fn_end=1037078, fn_end%104=94, fn_begin=1037070, fn_begin%104=86
+fn_end=1037082, fn_end%104=98, fn_begin=1037075, fn_begin%104=91
+fn_end=1037086, fn_end%104=102, fn_begin=1037079, fn_begin%104=95
+fn_end=1037091, fn_end%104=3, fn_begin=1037083, fn_begin%104=99
+fn_end=1037095, fn_end%104=7, fn_begin=1037088, fn_begin%104=0
+fn_end=1037099, fn_end%104=11, fn_begin=1037092, fn_begin%104=4
+fn_end=1037104, fn_end%104=16, fn_begin=1037096, fn_begin%104=8
+fn_end=1037108, fn_end%104=20, fn_begin=1037101, fn_begin%104=13
+fn_end=1037112, fn_end%104=24, fn_begin=1037105, fn_begin%104=17
+fn_end=1037117, fn_end%104=29, fn_begin=1037109, fn_begin%104=21
+fn_end=1037121, fn_end%104=33, fn_begin=1037114, fn_begin%104=26
+fn_end=1037125, fn_end%104=37, fn_begin=1037118, fn_begin%104=30
+fn_end=1037130, fn_end%104=42, fn_begin=1037122, fn_begin%104=34
+fn_end=1037134, fn_end%104=46, fn_begin=1037127, fn_begin%104=39
+fn_end=1037138, fn_end%104=50, fn_begin=1037131, fn_begin%104=43
+fn_end=1037143, fn_end%104=55, fn_begin=1037135, fn_begin%104=47
+fn_end=1037147, fn_end%104=59, fn_begin=1037140, fn_begin%104=52
+fn_end=1037151, fn_end%104=63, fn_begin=1037144, fn_begin%104=56
+fn_end=1037156, fn_end%104=68, fn_begin=1037148, fn_begin%104=60
+fn_end=1037160, fn_end%104=72, fn_begin=1037153, fn_begin%104=65
+fn_end=1037164, fn_end%104=76, fn_begin=1037157, fn_begin%104=69
+fn_end=1037169, fn_end%104=81, fn_begin=1037161, fn_begin%104=73
+fn_end=1037173, fn_end%104=85, fn_begin=1037166, fn_begin%104=78
+fn_end=1037177, fn_end%104=89, fn_begin=1037170, fn_begin%104=82
+fn_end=1037182, fn_end%104=94, fn_begin=1037174, fn_begin%104=86
+fn_end=1037186, fn_end%104=98, fn_begin=1037179, fn_begin%104=91
+fn_end=1037190, fn_end%104=102, fn_begin=1037183, fn_begin%104=95
+fn_end=1037195, fn_end%104=3, fn_begin=1037187, fn_begin%104=99
+fn_end=1037199, fn_end%104=7, fn_begin=1037192, fn_begin%104=0
+fn_end=1037203, fn_end%104=11, fn_begin=1037196, fn_begin%104=4
+fn_end=1037208, fn_end%104=16, fn_begin=1037200, fn_begin%104=8
+fn_end=1037212, fn_end%104=20, fn_begin=1037205, fn_begin%104=13
+fn_end=1037216, fn_end%104=24, fn_begin=1037209, fn_begin%104=17
+fn_end=1037221, fn_end%104=29, fn_begin=1037213, fn_begin%104=21
+fn_end=1037225, fn_end%104=33, fn_begin=1037218, fn_begin%104=26
+fn_end=1037229, fn_end%104=37, fn_begin=1037222, fn_begin%104=30
+fn_end=1037234, fn_end%104=42, fn_begin=1037226, fn_begin%104=34
+fn_end=1037238, fn_end%104=46, fn_begin=1037231, fn_begin%104=39
+fn_end=1037242, fn_end%104=50, fn_begin=1037235, fn_begin%104=43
+fn_end=1037247, fn_end%104=55, fn_begin=1037239, fn_begin%104=47
+fn_end=1037251, fn_end%104=59, fn_begin=1037244, fn_begin%104=52
+fn_end=1037255, fn_end%104=63, fn_begin=1037248, fn_begin%104=56
+fn_end=1037260, fn_end%104=68, fn_begin=1037252, fn_begin%104=60
+fn_end=1037264, fn_end%104=72, fn_begin=1037257, fn_begin%104=65
+fn_end=1037268, fn_end%104=76, fn_begin=1037261, fn_begin%104=69
+fn_end=1037273, fn_end%104=81, fn_begin=1037265, fn_begin%104=73
+fn_end=1037277, fn_end%104=85, fn_begin=1037270, fn_begin%104=78
+fn_end=1037281, fn_end%104=89, fn_begin=1037274, fn_begin%104=82
+fn_end=1037286, fn_end%104=94, fn_begin=1037278, fn_begin%104=86
+fn_end=1037290, fn_end%104=98, fn_begin=1037283, fn_begin%104=91
+fn_end=1037294, fn_end%104=102, fn_begin=1037287, fn_begin%104=95
+
+TCH/H0
+fn_end=1175826, fn_end%104=2, fn_begin=1175819, fn_begin%104=99
+fn_end=1175830, fn_end%104=6, fn_begin=1175824, fn_begin%104=0
+fn_end=1175834, fn_end%104=10, fn_begin=1175828, fn_begin%104=4
+fn_end=1175839, fn_end%104=15, fn_begin=1175832, fn_begin%104=8
+fn_end=1175843, fn_end%104=19, fn_begin=1175837, fn_begin%104=13
+fn_end=1175847, fn_end%104=23, fn_begin=1175841, fn_begin%104=17
+fn_end=1175852, fn_end%104=28, fn_begin=1175845, fn_begin%104=21
+fn_end=1175856, fn_end%104=32, fn_begin=1175850, fn_begin%104=26
+fn_end=1175860, fn_end%104=36, fn_begin=1175854, fn_begin%104=30
+fn_end=1175865, fn_end%104=41, fn_begin=1175858, fn_begin%104=34
+fn_end=1175869, fn_end%104=45, fn_begin=1175863, fn_begin%104=39
+fn_end=1175873, fn_end%104=49, fn_begin=1175867, fn_begin%104=43
+fn_end=1175878, fn_end%104=54, fn_begin=1175871, fn_begin%104=47
+fn_end=1175882, fn_end%104=58, fn_begin=1175876, fn_begin%104=52
+fn_end=1175886, fn_end%104=62, fn_begin=1175880, fn_begin%104=56
+fn_end=1175891, fn_end%104=67, fn_begin=1175884, fn_begin%104=60
+fn_end=1175895, fn_end%104=71, fn_begin=1175889, fn_begin%104=65
+fn_end=1175899, fn_end%104=75, fn_begin=1175893, fn_begin%104=69
+fn_end=1175904, fn_end%104=80, fn_begin=1175897, fn_begin%104=73
+fn_end=1175908, fn_end%104=84, fn_begin=1175902, fn_begin%104=78
+fn_end=1175912, fn_end%104=88, fn_begin=1175906, fn_begin%104=82
+fn_end=1175917, fn_end%104=93, fn_begin=1175910, fn_begin%104=86
+fn_end=1175921, fn_end%104=97, fn_begin=1175915, fn_begin%104=91
+fn_end=1175925, fn_end%104=101, fn_begin=1175919, fn_begin%104=95
+fn_end=1175930, fn_end%104=2, fn_begin=1175923, fn_begin%104=99
+fn_end=1175934, fn_end%104=6, fn_begin=1175928, fn_begin%104=0
+fn_end=1175938, fn_end%104=10, fn_begin=1175932, fn_begin%104=4
+fn_end=1175943, fn_end%104=15, fn_begin=1175936, fn_begin%104=8
+fn_end=1175947, fn_end%104=19, fn_begin=1175941, fn_begin%104=13
+fn_end=1175951, fn_end%104=23, fn_begin=1175945, fn_begin%104=17
+fn_end=1175956, fn_end%104=28, fn_begin=1175949, fn_begin%104=21
+fn_end=1175960, fn_end%104=32, fn_begin=1175954, fn_begin%104=26
+fn_end=1175964, fn_end%104=36, fn_begin=1175958, fn_begin%104=30
+fn_end=1175969, fn_end%104=41, fn_begin=1175962, fn_begin%104=34
+fn_end=1175973, fn_end%104=45, fn_begin=1175967, fn_begin%104=39
+fn_end=1175977, fn_end%104=49, fn_begin=1175971, fn_begin%104=43
+fn_end=1175982, fn_end%104=54, fn_begin=1175975, fn_begin%104=47
+fn_end=1175986, fn_end%104=58, fn_begin=1175980, fn_begin%104=52
+fn_end=1175990, fn_end%104=62, fn_begin=1175984, fn_begin%104=56
+fn_end=1175995, fn_end%104=67, fn_begin=1175988, fn_begin%104=60
+fn_end=1175999, fn_end%104=71, fn_begin=1175993, fn_begin%104=65
+fn_end=1176003, fn_end%104=75, fn_begin=1175997, fn_begin%104=69
+fn_end=1176008, fn_end%104=80, fn_begin=1176001, fn_begin%104=73
+fn_end=1176012, fn_end%104=84, fn_begin=1176006, fn_begin%104=78
+fn_end=1176016, fn_end%104=88, fn_begin=1176010, fn_begin%104=82
+fn_end=1176021, fn_end%104=93, fn_begin=1176014, fn_begin%104=86
+fn_end=1176025, fn_end%104=97, fn_begin=1176019, fn_begin%104=91
+fn_end=1176029, fn_end%104=101, fn_begin=1176023, fn_begin%104=95
+fn_end=1176034, fn_end%104=2, fn_begin=1176027, fn_begin%104=99
+fn_end=1176038, fn_end%104=6, fn_begin=1176032, fn_begin%104=0
+fn_end=1176042, fn_end%104=10, fn_begin=1176036, fn_begin%104=4
+fn_end=1176047, fn_end%104=15, fn_begin=1176040, fn_begin%104=8
+fn_end=1176051, fn_end%104=19, fn_begin=1176045, fn_begin%104=13
+fn_end=1176055, fn_end%104=23, fn_begin=1176049, fn_begin%104=17
+fn_end=1176060, fn_end%104=28, fn_begin=1176053, fn_begin%104=21
+fn_end=1176064, fn_end%104=32, fn_begin=1176058, fn_begin%104=26
+fn_end=1176068, fn_end%104=36, fn_begin=1176062, fn_begin%104=30
+fn_end=1176073, fn_end%104=41, fn_begin=1176066, fn_begin%104=34
+fn_end=1176077, fn_end%104=45, fn_begin=1176071, fn_begin%104=39
+fn_end=1176081, fn_end%104=49, fn_begin=1176075, fn_begin%104=43
+fn_end=1176086, fn_end%104=54, fn_begin=1176079, fn_begin%104=47
+fn_end=1176090, fn_end%104=58, fn_begin=1176084, fn_begin%104=52
+fn_end=1176094, fn_end%104=62, fn_begin=1176088, fn_begin%104=56
+fn_end=1176099, fn_end%104=67, fn_begin=1176092, fn_begin%104=60
+fn_end=1176103, fn_end%104=71, fn_begin=1176097, fn_begin%104=65
+fn_end=1176107, fn_end%104=75, fn_begin=1176101, fn_begin%104=69
+fn_end=1176112, fn_end%104=80, fn_begin=1176105, fn_begin%104=73
+fn_end=1176116, fn_end%104=84, fn_begin=1176110, fn_begin%104=78
+fn_end=1176120, fn_end%104=88, fn_begin=1176114, fn_begin%104=82
+fn_end=1176125, fn_end%104=93, fn_begin=1176118, fn_begin%104=86
+fn_end=1176129, fn_end%104=97, fn_begin=1176123, fn_begin%104=91
+fn_end=1176133, fn_end%104=101, fn_begin=1176127, fn_begin%104=95
+
+TCH/H1
+fn_end=1175827, fn_end%104=3, fn_begin=1175820, fn_begin%104=100
+fn_end=1175831, fn_end%104=7, fn_begin=1175825, fn_begin%104=1
+fn_end=1175835, fn_end%104=11, fn_begin=1175829, fn_begin%104=5
+fn_end=1175840, fn_end%104=16, fn_begin=1175833, fn_begin%104=9
+fn_end=1175844, fn_end%104=20, fn_begin=1175838, fn_begin%104=14
+fn_end=1175848, fn_end%104=24, fn_begin=1175842, fn_begin%104=18
+fn_end=1175853, fn_end%104=29, fn_begin=1175846, fn_begin%104=22
+fn_end=1175857, fn_end%104=33, fn_begin=1175851, fn_begin%104=27
+fn_end=1175861, fn_end%104=37, fn_begin=1175855, fn_begin%104=31
+fn_end=1175866, fn_end%104=42, fn_begin=1175859, fn_begin%104=35
+fn_end=1175870, fn_end%104=46, fn_begin=1175864, fn_begin%104=40
+fn_end=1175874, fn_end%104=50, fn_begin=1175868, fn_begin%104=44
+fn_end=1175879, fn_end%104=55, fn_begin=1175872, fn_begin%104=48
+fn_end=1175883, fn_end%104=59, fn_begin=1175877, fn_begin%104=53
+fn_end=1175887, fn_end%104=63, fn_begin=1175881, fn_begin%104=57
+fn_end=1175892, fn_end%104=68, fn_begin=1175885, fn_begin%104=61
+fn_end=1175896, fn_end%104=72, fn_begin=1175890, fn_begin%104=66
+fn_end=1175900, fn_end%104=76, fn_begin=1175894, fn_begin%104=70
+fn_end=1175905, fn_end%104=81, fn_begin=1175898, fn_begin%104=74
+fn_end=1175909, fn_end%104=85, fn_begin=1175903, fn_begin%104=79
+fn_end=1175913, fn_end%104=89, fn_begin=1175907, fn_begin%104=83
+fn_end=1175918, fn_end%104=94, fn_begin=1175911, fn_begin%104=87
+fn_end=1175922, fn_end%104=98, fn_begin=1175916, fn_begin%104=92
+fn_end=1175926, fn_end%104=102, fn_begin=1175920, fn_begin%104=96
+fn_end=1175931, fn_end%104=3, fn_begin=1175924, fn_begin%104=100
+fn_end=1175935, fn_end%104=7, fn_begin=1175929, fn_begin%104=1
+fn_end=1175939, fn_end%104=11, fn_begin=1175933, fn_begin%104=5
+fn_end=1175944, fn_end%104=16, fn_begin=1175937, fn_begin%104=9
+fn_end=1175948, fn_end%104=20, fn_begin=1175942, fn_begin%104=14
+fn_end=1175952, fn_end%104=24, fn_begin=1175946, fn_begin%104=18
+fn_end=1175957, fn_end%104=29, fn_begin=1175950, fn_begin%104=22
+fn_end=1175961, fn_end%104=33, fn_begin=1175955, fn_begin%104=27
+fn_end=1175965, fn_end%104=37, fn_begin=1175959, fn_begin%104=31
+fn_end=1175970, fn_end%104=42, fn_begin=1175963, fn_begin%104=35
+fn_end=1175974, fn_end%104=46, fn_begin=1175968, fn_begin%104=40
+fn_end=1175978, fn_end%104=50, fn_begin=1175972, fn_begin%104=44
+fn_end=1175983, fn_end%104=55, fn_begin=1175976, fn_begin%104=48
+fn_end=1175987, fn_end%104=59, fn_begin=1175981, fn_begin%104=53
+fn_end=1175991, fn_end%104=63, fn_begin=1175985, fn_begin%104=57
+fn_end=1175996, fn_end%104=68, fn_begin=1175989, fn_begin%104=61
+fn_end=1176000, fn_end%104=72, fn_begin=1175994, fn_begin%104=66
+fn_end=1176004, fn_end%104=76, fn_begin=1175998, fn_begin%104=70
+fn_end=1176009, fn_end%104=81, fn_begin=1176002, fn_begin%104=74
+fn_end=1176013, fn_end%104=85, fn_begin=1176007, fn_begin%104=79
+fn_end=1176017, fn_end%104=89, fn_begin=1176011, fn_begin%104=83
+fn_end=1176022, fn_end%104=94, fn_begin=1176015, fn_begin%104=87
+fn_end=1176026, fn_end%104=98, fn_begin=1176020, fn_begin%104=92
+fn_end=1176030, fn_end%104=102, fn_begin=1176024, fn_begin%104=96
+fn_end=1176035, fn_end%104=3, fn_begin=1176028, fn_begin%104=100
+fn_end=1176039, fn_end%104=7, fn_begin=1176033, fn_begin%104=1
+fn_end=1176043, fn_end%104=11, fn_begin=1176037, fn_begin%104=5
+fn_end=1176048, fn_end%104=16, fn_begin=1176041, fn_begin%104=9
+fn_end=1176052, fn_end%104=20, fn_begin=1176046, fn_begin%104=14
+fn_end=1176056, fn_end%104=24, fn_begin=1176050, fn_begin%104=18
+fn_end=1176061, fn_end%104=29, fn_begin=1176054, fn_begin%104=22
+fn_end=1176065, fn_end%104=33, fn_begin=1176059, fn_begin%104=27
+fn_end=1176069, fn_end%104=37, fn_begin=1176063, fn_begin%104=31
+fn_end=1176074, fn_end%104=42, fn_begin=1176067, fn_begin%104=35
+fn_end=1176078, fn_end%104=46, fn_begin=1176072, fn_begin%104=40
+fn_end=1176082, fn_end%104=50, fn_begin=1176076, fn_begin%104=44
+fn_end=1176087, fn_end%104=55, fn_begin=1176080, fn_begin%104=48
+fn_end=1176091, fn_end%104=59, fn_begin=1176085, fn_begin%104=53
+fn_end=1176095, fn_end%104=63, fn_begin=1176089, fn_begin%104=57
+fn_end=1176100, fn_end%104=68, fn_begin=1176093, fn_begin%104=61
+fn_end=1176104, fn_end%104=72, fn_begin=1176098, fn_begin%104=66
+fn_end=1176108, fn_end%104=76, fn_begin=1176102, fn_begin%104=70
+fn_end=1176113, fn_end%104=81, fn_begin=1176106, fn_begin%104=74
+fn_end=1176117, fn_end%104=85, fn_begin=1176111, fn_begin%104=79
+fn_end=1176121, fn_end%104=89, fn_begin=1176115, fn_begin%104=83
+fn_end=1176126, fn_end%104=94, fn_begin=1176119, fn_begin%104=87
+fn_end=1176130, fn_end%104=98, fn_begin=1176124, fn_begin%104=92
+fn_end=1176134, fn_end%104=102, fn_begin=1176128, fn_begin%104=96
+
+FACCH/F
+fn_end=177275, fn_end%104=59, fn_begin=177268, fn_begin%104=52
+fn_end=177314, fn_end%104=98, fn_begin=177307, fn_begin%104=91
+fn_end=177336, fn_end%104=16, fn_begin=177328, fn_begin%104=8
+fn_end=177375, fn_end%104=55, fn_begin=177367, fn_begin%104=47
+fn_end=177396, fn_end%104=76, fn_begin=177389, fn_begin%104=69
+fn_end=177435, fn_end%104=11, fn_begin=177428, fn_begin%104=4
+fn_end=178328, fn_end%104=72, fn_begin=178321, fn_begin%104=65
+fn_end=178367, fn_end%104=7, fn_begin=178360, fn_begin%104=0
+fn_end=178393, fn_end%104=33, fn_begin=178386, fn_begin%104=26
+fn_end=180014, fn_end%104=94, fn_begin=180006, fn_begin%104=86
+fn_end=180053, fn_end%104=29, fn_begin=180045, fn_begin%104=21
+fn_end=180079, fn_end%104=55, fn_begin=180071, fn_begin%104=47
+fn_end=180113, fn_end%104=89, fn_begin=180106, fn_begin%104=82
+fn_end=180144, fn_end%104=16, fn_begin=180136, fn_begin%104=8
+fn_end=180183, fn_end%104=55, fn_begin=180175, fn_begin%104=47
+
+FACCH/H0
+fn_end=499956, fn_end%104=28, fn_begin=499945, fn_begin%104=17
+fn_end=499999, fn_end%104=71, fn_begin=499988, fn_begin%104=60
+fn_end=500034, fn_end%104=2, fn_begin=500023, fn_begin%104=95
+fn_end=500077, fn_end%104=45, fn_begin=500066, fn_begin%104=34
+fn_end=500952, fn_end%104=88, fn_begin=500942, fn_begin%104=78
+fn_end=501836, fn_end%104=36, fn_begin=501826, fn_begin%104=26
+fn_end=501880, fn_end%104=80, fn_begin=501869, fn_begin%104=69
+fn_end=502850, fn_end%104=10, fn_begin=502840, fn_begin%104=0
+fn_end=502894, fn_end%104=54, fn_begin=502883, fn_begin%104=43
+fn_end=502937, fn_end%104=97, fn_begin=502926, fn_begin%104=86
+fn_end=503006, fn_end%104=62, fn_begin=502996, fn_begin%104=52
+fn_end=503050, fn_end%104=2, fn_begin=503039, fn_begin%104=95
+
+FACCH/H1
+fn_end=500728, fn_end%104=72, fn_begin=500717, fn_begin%104=61
+fn_end=500771, fn_end%104=11, fn_begin=500761, fn_begin%104=1
+fn_end=500797, fn_end%104=37, fn_begin=500787, fn_begin%104=27
+fn_end=500841, fn_end%104=81, fn_begin=500830, fn_begin%104=70
+fn_end=500875, fn_end%104=11, fn_begin=500865, fn_begin%104=1
+fn_end=500919, fn_end%104=55, fn_begin=500908, fn_begin%104=44
+fn_end=501751, fn_end%104=55, fn_begin=501740, fn_begin%104=44
+fn_end=501794, fn_end%104=98, fn_begin=501783, fn_begin%104=87
+fn_end=501837, fn_end%104=37, fn_begin=501827, fn_begin%104=27
+fn_end=502782, fn_end%104=46, fn_begin=502771, fn_begin%104=35
+fn_end=502825, fn_end%104=89, fn_begin=502815, fn_begin%104=79
+fn_end=502869, fn_end%104=29, fn_begin=502858, fn_begin%104=18
+fn_end=502903, fn_end%104=63, fn_begin=502893, fn_begin%104=53
+fn_end=502955, fn_end%104=11, fn_begin=502945, fn_begin%104=1
+fn_end=502999, fn_end%104=55, fn_begin=502988, fn_begin%104=44
+
diff --git a/tests/gsm29205/gsm29205_test.c b/tests/gsm29205/gsm29205_test.c
index 5add1b18..44c3453c 100644
--- a/tests/gsm29205/gsm29205_test.c
+++ b/tests/gsm29205/gsm29205_test.c
@@ -44,7 +44,13 @@ static void test_gcr()
};
uint8_t len;
struct msgb *msg;
- struct osmo_gcr_parsed p = { 0 }, g = {
+ struct osmo_gcr_parsed p = {
+ .net_len = 0,
+ .net = { 0 },
+ .node = 0x00,
+ .cr = { 0 },
+ };
+ struct osmo_gcr_parsed g = {
.net_len = 3,
.net = { 0x51, 0x52, 0x53 },
.node = 0xDEAD,
diff --git a/tests/gsup/gsup_test.err b/tests/gsup/gsup_test.err
index e5fe6ee3..1da39649 100644
--- a/tests/gsup/gsup_test.err
+++ b/tests/gsup/gsup_test.err
@@ -157,27 +157,27 @@
DLGSUP Stopping DLGSUP logging
message 0: tested 3584 modifications, 771 parse failures
message 1: tested 3584 modifications, 770 parse failures
- message 2: tested 21248 modifications, 2575 parse failures
+ message 2: tested 21248 modifications, 2577 parse failures
message 3: tested 2816 modifications, 510 parse failures
message 4: tested 3584 modifications, 770 parse failures
- message 5: tested 20736 modifications, 4023 parse failures
+ message 5: tested 20736 modifications, 4025 parse failures
message 6: tested 3584 modifications, 771 parse failures
message 7: tested 3584 modifications, 770 parse failures
message 8: tested 2816 modifications, 510 parse failures
message 9: tested 2816 modifications, 510 parse failures
message 10: tested 3584 modifications, 770 parse failures
message 11: tested 3328 modifications, 770 parse failures
- message 12: tested 54016 modifications, 4626 parse failures
- message 13: tested 11520 modifications, 1026 parse failures
+ message 12: tested 54016 modifications, 4628 parse failures
+ message 13: tested 11520 modifications, 1028 parse failures
message 14: tested 5120 modifications, 1030 parse failures
- message 15: tested 10752 modifications, 1262 parse failures
+ message 15: tested 10752 modifications, 1263 parse failures
message 16: tested 7680 modifications, 1271 parse failures
message 17: tested 8448 modifications, 2053 parse failures
- message 18: tested 11264 modifications, 2307 parse failures
+ message 18: tested 11264 modifications, 2308 parse failures
message 19: tested 5120 modifications, 1031 parse failures
message 20: tested 4352 modifications, 1030 parse failures
message 21: tested 3584 modifications, 771 parse failures
- message 22: tested 5632 modifications, 771 parse failures
+ message 22: tested 5632 modifications, 772 parse failures
message 23: tested 3584 modifications, 770 parse failures
message 24: tested 3584 modifications, 771 parse failures
message 25: tested 11264 modifications, 2058 parse failures
diff --git a/tests/logging/logging_vty_test.c b/tests/logging/logging_vty_test.c
index 30426f39..e7019f61 100644
--- a/tests/logging/logging_vty_test.c
+++ b/tests/logging/logging_vty_test.c
@@ -241,6 +241,7 @@ int main(int argc, char **argv)
vty_init(&vty_info);
osmo_init_logging2(root_ctx, &log_info);
+ log_enable_multithread();
vty_commands_init();
diff --git a/tests/sockaddr_str/sockaddr_str_test.c b/tests/sockaddr_str/sockaddr_str_test.c
index d2e7944d..bf7d7380 100644
--- a/tests/sockaddr_str/sockaddr_str_test.c
+++ b/tests/sockaddr_str/sockaddr_str_test.c
@@ -53,6 +53,8 @@ struct osmo_sockaddr_str oip_data[] = {
{ .af = AF_INET, .ip = "1.2.3.4", .port = 0 },
{ .af = AF_INET, .ip = "1.2.3:4:5", .port = 0 },
{ .af = AF_INET6, .ip = "::1:10.9.8.7", .port = 1 },
+ { .af = AF_INET, .ip = "0.0.0.0", .port = 5 },
+ { .af = AF_INET6, .ip = "::", .port = 5 },
};
const char *af_name(int af)
@@ -105,7 +107,10 @@ void sockaddr_str_test_conversions()
printf("\n\n");
dump_oip(x);
+ printf(" OSMO_SOCKADDR_STR_FMT: '" OSMO_SOCKADDR_STR_FMT "'\n",
+ OSMO_SOCKADDR_STR_FMT_ARGS(x));
printf(" osmo_sockaddr_str_is_set() = %s\n", osmo_sockaddr_str_is_set(x) ? "true" : "false");
+ printf(" osmo_sockaddr_str_is_nonzero() = %s\n", osmo_sockaddr_str_is_nonzero(x) ? "true" : "false");
{
struct in_addr a = {};
diff --git a/tests/sockaddr_str/sockaddr_str_test.ok b/tests/sockaddr_str/sockaddr_str_test.ok
index d69314d4..bc18225d 100644
--- a/tests/sockaddr_str/sockaddr_str_test.ok
+++ b/tests/sockaddr_str/sockaddr_str_test.ok
@@ -1,7 +1,9 @@
{ .af = AF_INET, .ip = "1.2.3.4", .port = 5 }
+ OSMO_SOCKADDR_STR_FMT: '1.2.3.4:5'
osmo_sockaddr_str_is_set() = true
+ osmo_sockaddr_str_is_nonzero() = true
osmo_sockaddr_str_to_in_addr() rc == 0 in_addr=01020304
-> osmo_sockaddr_str_from_in_addr() rc == 0 { .af = AF_INET, .ip = "1.2.3.4", .port = 5 }
osmo_sockaddr_str_to_in6_addr() rc < 0 in6_addr=00000000000000000000000000000000
@@ -18,7 +20,9 @@
{ .af = AF_INET, .ip = "0.0.0.0", .port = 0 }
+ OSMO_SOCKADDR_STR_FMT: '0.0.0.0:0'
osmo_sockaddr_str_is_set() = false
+ osmo_sockaddr_str_is_nonzero() = false
osmo_sockaddr_str_to_in_addr() rc == 0 in_addr=00000000
-> osmo_sockaddr_str_from_in_addr() rc == 0 { .af = AF_INET, .ip = "0.0.0.0", .port = 0 }
osmo_sockaddr_str_to_in6_addr() rc < 0 in6_addr=00000000000000000000000000000000
@@ -35,7 +39,9 @@
{ .af = AF_INET, .ip = "255.255.255.255", .port = 65535 }
+ OSMO_SOCKADDR_STR_FMT: '255.255.255.255:65535'
osmo_sockaddr_str_is_set() = true
+ osmo_sockaddr_str_is_nonzero() = true
osmo_sockaddr_str_to_in_addr() rc == 0 in_addr=ffffffff
-> osmo_sockaddr_str_from_in_addr() rc == 0 { .af = AF_INET, .ip = "255.255.255.255", .port = 65535 }
osmo_sockaddr_str_to_in6_addr() rc < 0 in6_addr=00000000000000000000000000000000
@@ -52,7 +58,9 @@
{ .af = AF_INET, .ip = "0.0.0.256", .port = 1 }
+ OSMO_SOCKADDR_STR_FMT: '0.0.0.256:1'
osmo_sockaddr_str_is_set() = true
+ osmo_sockaddr_str_is_nonzero() = false
osmo_sockaddr_str_to_in_addr() rc < 0 in_addr=00000000
osmo_sockaddr_str_to_in6_addr() rc < 0 in6_addr=00000000000000000000000000000000
osmo_sockaddr_str_to_32() rc < 0 uint32_t=0x0
@@ -64,7 +72,9 @@
{ .af = AF_INET, .ip = "not an ip address", .port = 1 }
+ OSMO_SOCKADDR_STR_FMT: 'not an ip address:1'
osmo_sockaddr_str_is_set() = true
+ osmo_sockaddr_str_is_nonzero() = false
osmo_sockaddr_str_to_in_addr() rc < 0 in_addr=00000000
osmo_sockaddr_str_to_in6_addr() rc < 0 in6_addr=00000000000000000000000000000000
osmo_sockaddr_str_to_32() rc < 0 uint32_t=0x0
@@ -76,7 +86,9 @@
{ .af = AF_INET6, .ip = "1:2:3::4", .port = 5 }
+ OSMO_SOCKADDR_STR_FMT: '[1:2:3::4]:5'
osmo_sockaddr_str_is_set() = true
+ osmo_sockaddr_str_is_nonzero() = true
osmo_sockaddr_str_to_in_addr() rc < 0 in_addr=00000000
osmo_sockaddr_str_to_in6_addr() rc == 0 in6_addr=00010002000300000000000000000004
-> osmo_sockaddr_str_from_in6_addr() rc == 0 { .af = AF_INET6, .ip = "1:2:3::4", .port = 5 }
@@ -91,7 +103,9 @@
{ .af = AF_INET6, .ip = "::", .port = 0 }
+ OSMO_SOCKADDR_STR_FMT: '[::]:0'
osmo_sockaddr_str_is_set() = false
+ osmo_sockaddr_str_is_nonzero() = false
osmo_sockaddr_str_to_in_addr() rc < 0 in_addr=00000000
osmo_sockaddr_str_to_in6_addr() rc == 0 in6_addr=00000000000000000000000000000000
-> osmo_sockaddr_str_from_in6_addr() rc == 0 { .af = AF_INET6, .ip = "::", .port = 0 }
@@ -106,7 +120,9 @@
{ .af = AF_INET6, .ip = "::1", .port = 0 }
+ OSMO_SOCKADDR_STR_FMT: '[::1]:0'
osmo_sockaddr_str_is_set() = false
+ osmo_sockaddr_str_is_nonzero() = false
osmo_sockaddr_str_to_in_addr() rc < 0 in_addr=00000000
osmo_sockaddr_str_to_in6_addr() rc == 0 in6_addr=00000000000000000000000000000001
-> osmo_sockaddr_str_from_in6_addr() rc == 0 { .af = AF_INET6, .ip = "::1", .port = 0 }
@@ -121,7 +137,9 @@
{ .af = AF_INET6, .ip = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", .port = 65535 }
+ OSMO_SOCKADDR_STR_FMT: '[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:65535'
osmo_sockaddr_str_is_set() = true
+ osmo_sockaddr_str_is_nonzero() = true
osmo_sockaddr_str_to_in_addr() rc < 0 in_addr=00000000
osmo_sockaddr_str_to_in6_addr() rc == 0 in6_addr=ffffffffffffffffffffffffffffffff
-> osmo_sockaddr_str_from_in6_addr() rc == 0 { .af = AF_INET6, .ip = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", .port = 65535 }
@@ -136,7 +154,9 @@
{ .af = AF_INET6, .ip = "FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF", .port = 65535 }
+ OSMO_SOCKADDR_STR_FMT: '[FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF:FFFF]:65535'
osmo_sockaddr_str_is_set() = true
+ osmo_sockaddr_str_is_nonzero() = true
osmo_sockaddr_str_to_in_addr() rc < 0 in_addr=00000000
osmo_sockaddr_str_to_in6_addr() rc == 0 in6_addr=ffffffffffffffffffffffffffffffff
-> osmo_sockaddr_str_from_in6_addr() rc == 0 { .af = AF_INET6, .ip = "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", .port = 65535 }
@@ -154,7 +174,9 @@
{ .af = AF_INET6, .ip = "::fffff", .port = 1 }
+ OSMO_SOCKADDR_STR_FMT: '[::fffff]:1'
osmo_sockaddr_str_is_set() = true
+ osmo_sockaddr_str_is_nonzero() = false
osmo_sockaddr_str_to_in_addr() rc < 0 in_addr=00000000
osmo_sockaddr_str_to_in6_addr() rc < 0 in6_addr=00000000000000000000000000000000
osmo_sockaddr_str_to_32() rc < 0 uint32_t=0x0
@@ -166,7 +188,9 @@
{ .af = AF_INET6, .ip = "not an ip address", .port = 1 }
+ OSMO_SOCKADDR_STR_FMT: '[not an ip address]:1'
osmo_sockaddr_str_is_set() = true
+ osmo_sockaddr_str_is_nonzero() = false
osmo_sockaddr_str_to_in_addr() rc < 0 in_addr=00000000
osmo_sockaddr_str_to_in6_addr() rc < 0 in6_addr=00000000000000000000000000000000
osmo_sockaddr_str_to_32() rc < 0 uint32_t=0x0
@@ -178,7 +202,9 @@
{ .af = AF_INET6, .ip = "1.2.3.4", .port = 5 }
+ OSMO_SOCKADDR_STR_FMT: '[1.2.3.4]:5'
osmo_sockaddr_str_is_set() = true
+ osmo_sockaddr_str_is_nonzero() = false
osmo_sockaddr_str_to_in_addr() rc < 0 in_addr=00000000
osmo_sockaddr_str_to_in6_addr() rc < 0 in6_addr=00000000000000000000000000000000
osmo_sockaddr_str_to_32() rc < 0 uint32_t=0x0
@@ -191,7 +217,9 @@
{ .af = AF_INET, .ip = "1:2:3::4", .port = 5 }
+ OSMO_SOCKADDR_STR_FMT: '1:2:3::4:5'
osmo_sockaddr_str_is_set() = true
+ osmo_sockaddr_str_is_nonzero() = false
osmo_sockaddr_str_to_in_addr() rc < 0 in_addr=00000000
osmo_sockaddr_str_to_in6_addr() rc < 0 in6_addr=00000000000000000000000000000000
osmo_sockaddr_str_to_32() rc < 0 uint32_t=0x0
@@ -204,7 +232,9 @@
{ .af = AF_UNSPEC, .ip = "1.2.3.4", .port = 5 }
+ OSMO_SOCKADDR_STR_FMT: '1.2.3.4:5'
osmo_sockaddr_str_is_set() = false
+ osmo_sockaddr_str_is_nonzero() = false
osmo_sockaddr_str_to_in_addr() rc < 0 in_addr=00000000
osmo_sockaddr_str_to_in6_addr() rc < 0 in6_addr=00000000000000000000000000000000
osmo_sockaddr_str_to_32() rc < 0 uint32_t=0x0
@@ -217,7 +247,9 @@
{ .af = AF_INET, .ip = "", .port = 5 }
+ OSMO_SOCKADDR_STR_FMT: ':5'
osmo_sockaddr_str_is_set() = false
+ osmo_sockaddr_str_is_nonzero() = false
osmo_sockaddr_str_to_in_addr() rc < 0 in_addr=00000000
osmo_sockaddr_str_to_in6_addr() rc < 0 in6_addr=00000000000000000000000000000000
osmo_sockaddr_str_to_32() rc < 0 uint32_t=0x0
@@ -229,7 +261,9 @@
{ .af = AF_INET6, .ip = "", .port = 5 }
+ OSMO_SOCKADDR_STR_FMT: '[]:5'
osmo_sockaddr_str_is_set() = false
+ osmo_sockaddr_str_is_nonzero() = false
osmo_sockaddr_str_to_in_addr() rc < 0 in_addr=00000000
osmo_sockaddr_str_to_in6_addr() rc < 0 in6_addr=00000000000000000000000000000000
osmo_sockaddr_str_to_32() rc < 0 uint32_t=0x0
@@ -241,7 +275,9 @@
{ .af = AF_INET, .ip = "1.2.3.4", .port = 0 }
+ OSMO_SOCKADDR_STR_FMT: '1.2.3.4:0'
osmo_sockaddr_str_is_set() = false
+ osmo_sockaddr_str_is_nonzero() = false
osmo_sockaddr_str_to_in_addr() rc == 0 in_addr=01020304
-> osmo_sockaddr_str_from_in_addr() rc == 0 { .af = AF_INET, .ip = "1.2.3.4", .port = 0 }
osmo_sockaddr_str_to_in6_addr() rc < 0 in6_addr=00000000000000000000000000000000
@@ -258,7 +294,9 @@
{ .af = AF_INET, .ip = "1.2.3:4:5", .port = 0 }
+ OSMO_SOCKADDR_STR_FMT: '1.2.3:4:5:0'
osmo_sockaddr_str_is_set() = false
+ osmo_sockaddr_str_is_nonzero() = false
osmo_sockaddr_str_to_in_addr() rc < 0 in_addr=00000000
osmo_sockaddr_str_to_in6_addr() rc < 0 in6_addr=00000000000000000000000000000000
osmo_sockaddr_str_to_32() rc < 0 uint32_t=0x0
@@ -271,7 +309,9 @@
{ .af = AF_INET6, .ip = "::1:10.9.8.7", .port = 1 }
+ OSMO_SOCKADDR_STR_FMT: '[::1:10.9.8.7]:1'
osmo_sockaddr_str_is_set() = true
+ osmo_sockaddr_str_is_nonzero() = true
osmo_sockaddr_str_to_in_addr() rc < 0 in_addr=00000000
osmo_sockaddr_str_to_in6_addr() rc == 0 in6_addr=0000000000000000000000010a090807
-> osmo_sockaddr_str_from_in6_addr() rc == 0 { .af = AF_INET6, .ip = "::1:a09:807", .port = 1 }
@@ -286,3 +326,39 @@
-> osmo_sockaddr_str_from_sockaddr() rc == 0 { .af = AF_INET6, .ip = "::1:a09:807", .port = 1 }
DIFFERS!
osmo_sockaddr_str_from_str() rc == 0 { .af = AF_INET6, .ip = "::1:10.9.8.7", .port = 1 }
+
+
+{ .af = AF_INET, .ip = "0.0.0.0", .port = 5 }
+ OSMO_SOCKADDR_STR_FMT: '0.0.0.0:5'
+ osmo_sockaddr_str_is_set() = true
+ osmo_sockaddr_str_is_nonzero() = false
+ osmo_sockaddr_str_to_in_addr() rc == 0 in_addr=00000000
+ -> osmo_sockaddr_str_from_in_addr() rc == 0 { .af = AF_INET, .ip = "0.0.0.0", .port = 5 }
+ osmo_sockaddr_str_to_in6_addr() rc < 0 in6_addr=00000000000000000000000000000000
+ osmo_sockaddr_str_to_32() rc == 0 uint32_t=0x0
+ -> osmo_sockaddr_str_from_32() rc == 0 { .af = AF_INET, .ip = "0.0.0.0", .port = 5 }
+ osmo_sockaddr_str_to_32n() rc == 0 uint32_t=0x0
+ -> osmo_sockaddr_str_from_32n() rc == 0 { .af = AF_INET, .ip = "0.0.0.0", .port = 5 }
+ osmo_sockaddr_str_to_sockaddr_in() rc == 0 sockaddr_in=02000005000000000000000000000000
+ -> osmo_sockaddr_str_from_sockaddr_in() rc == 0 { .af = AF_INET, .ip = "0.0.0.0", .port = 5 }
+ osmo_sockaddr_str_to_sockaddr_in6() rc < 0 sockaddr_in6=00000000000000000000000000000000000000000000000000000000
+ osmo_sockaddr_str_to_sockaddr() rc == 0 sockaddr_storage=0200000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ -> osmo_sockaddr_str_from_sockaddr() rc == 0 { .af = AF_INET, .ip = "0.0.0.0", .port = 5 }
+ osmo_sockaddr_str_from_str() rc == 0 { .af = AF_INET, .ip = "0.0.0.0", .port = 5 }
+
+
+{ .af = AF_INET6, .ip = "::", .port = 5 }
+ OSMO_SOCKADDR_STR_FMT: '[::]:5'
+ osmo_sockaddr_str_is_set() = true
+ osmo_sockaddr_str_is_nonzero() = false
+ osmo_sockaddr_str_to_in_addr() rc < 0 in_addr=00000000
+ osmo_sockaddr_str_to_in6_addr() rc == 0 in6_addr=00000000000000000000000000000000
+ -> osmo_sockaddr_str_from_in6_addr() rc == 0 { .af = AF_INET6, .ip = "::", .port = 5 }
+ osmo_sockaddr_str_to_32() rc < 0 uint32_t=0x0
+ osmo_sockaddr_str_to_32n() rc < 0 uint32_t=0x0
+ osmo_sockaddr_str_to_sockaddr_in() rc < 0 sockaddr_in=00000000000000000000000000000000
+ osmo_sockaddr_str_to_sockaddr_in6() rc == 0 sockaddr_in6=0a000005000000000000000000000000000000000000000000000000
+ -> osmo_sockaddr_str_from_sockaddr_in6() rc == 0 { .af = AF_INET6, .ip = "::", .port = 5 }
+ osmo_sockaddr_str_to_sockaddr() rc == 0 sockaddr_storage=0a00000500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
+ -> osmo_sockaddr_str_from_sockaddr() rc == 0 { .af = AF_INET6, .ip = "::", .port = 5 }
+ osmo_sockaddr_str_from_str() rc == 0 { .af = AF_INET6, .ip = "::", .port = 5 }
diff --git a/tests/tdef/tdef_test.c b/tests/tdef/tdef_test.c
index 12ca8020..276330a8 100644
--- a/tests/tdef/tdef_test.c
+++ b/tests/tdef/tdef_test.c
@@ -41,7 +41,7 @@ static struct osmo_tdef tdefs[] = {
{ .T=3, .default_val=100, .unit=OSMO_TDEF_M, .desc="100m" },
{ .T=4, .default_val=100, .unit=OSMO_TDEF_CUSTOM, .desc="100 potatoes" },
- { .T=7, .default_val=50, .desc="Water Boiling Timeout" }, // default is .unit=OSMO_TDEF_S == 0
+ { .T=7, .default_val=50, .desc="Water Boiling Timeout", .min_val=20, .max_val=800 }, // default is .unit=OSMO_TDEF_S == 0
{ .T=8, .default_val=300, .desc="Tea brewing" },
{ .T=9, .default_val=5, .unit=OSMO_TDEF_M, .desc="Let tea cool down before drinking" },
{ .T=10, .default_val=20, .unit=OSMO_TDEF_M, .desc="Forgot to drink tea while it's warm" },
@@ -143,8 +143,9 @@ static void test_tdef_set_and_get()
struct osmo_tdef *t;
printf("\n%s()\n", __func__);
- t = osmo_tdef_get_entry(tdefs, 7);
printf("setting 7 = 42\n");
+ t = osmo_tdef_get_entry(tdefs, 7);
+ OSMO_ASSERT(osmo_tdef_val_in_range(t, 42));
t->val = 42;
print_tdef_info(7);
print_tdef_get_short(tdefs, 7, OSMO_TDEF_MS);
@@ -153,13 +154,34 @@ static void test_tdef_set_and_get()
print_tdef_get_short(tdefs, 7, OSMO_TDEF_CUSTOM);
printf("setting 7 = 420\n");
- t->val = 420;
+ OSMO_ASSERT(osmo_tdef_set(tdefs, 7, 420, OSMO_TDEF_S) == 0);
+ print_tdef_info(7);
+ print_tdef_get_short(tdefs, 7, OSMO_TDEF_MS);
+ print_tdef_get_short(tdefs, 7, OSMO_TDEF_S);
+ print_tdef_get_short(tdefs, 7, OSMO_TDEF_M);
+ print_tdef_get_short(tdefs, 7, OSMO_TDEF_CUSTOM);
+
+ printf("setting 7 = 10 (ERANGE)\n");
+ OSMO_ASSERT(!osmo_tdef_val_in_range(t, 10));
+ OSMO_ASSERT(osmo_tdef_set(tdefs, 7, 10, OSMO_TDEF_S) == -ERANGE);
print_tdef_info(7);
print_tdef_get_short(tdefs, 7, OSMO_TDEF_MS);
print_tdef_get_short(tdefs, 7, OSMO_TDEF_S);
print_tdef_get_short(tdefs, 7, OSMO_TDEF_M);
print_tdef_get_short(tdefs, 7, OSMO_TDEF_CUSTOM);
+ printf("setting 7 = 900 (ERANGE)\n");
+ OSMO_ASSERT(!osmo_tdef_val_in_range(t, 900));
+ OSMO_ASSERT(osmo_tdef_set(tdefs, 7, 900, OSMO_TDEF_S) == -ERANGE);
+ print_tdef_info(7);
+ print_tdef_get_short(tdefs, 7, OSMO_TDEF_MS);
+ print_tdef_get_short(tdefs, 7, OSMO_TDEF_S);
+ print_tdef_get_short(tdefs, 7, OSMO_TDEF_M);
+ print_tdef_get_short(tdefs, 7, OSMO_TDEF_CUSTOM);
+
+ printf("setting 23 = 50 (EEXIST)\n");
+ OSMO_ASSERT(osmo_tdef_set(tdefs, 23, 50, OSMO_TDEF_S) == -EEXIST);
+
printf("resetting\n");
osmo_tdefs_reset(tdefs);
print_tdef_info(7);
diff --git a/tests/tdef/tdef_test.ok b/tests/tdef/tdef_test.ok
index d9ef99b2..3c4a0930 100644
--- a/tests/tdef/tdef_test.ok
+++ b/tests/tdef/tdef_test.ok
@@ -105,6 +105,19 @@ osmo_tdef_get(7, ms) = 420000
osmo_tdef_get(7, s) = 420
osmo_tdef_get(7, m) = 7
osmo_tdef_get(7, custom-unit) = 420
+setting 7 = 10 (ERANGE)
+T7=420s(def=50)
+osmo_tdef_get(7, ms) = 420000
+osmo_tdef_get(7, s) = 420
+osmo_tdef_get(7, m) = 7
+osmo_tdef_get(7, custom-unit) = 420
+setting 7 = 900 (ERANGE)
+T7=420s(def=50)
+osmo_tdef_get(7, ms) = 420000
+osmo_tdef_get(7, s) = 420
+osmo_tdef_get(7, m) = 7
+osmo_tdef_get(7, custom-unit) = 420
+setting 23 = 50 (EEXIST)
resetting
T7=50s
osmo_tdef_get(7, s) = 50
diff --git a/tests/tdef/tdef_vty_test_config_root.c b/tests/tdef/tdef_vty_test_config_root.c
index d69e0285..92113e87 100644
--- a/tests/tdef/tdef_vty_test_config_root.c
+++ b/tests/tdef/tdef_vty_test_config_root.c
@@ -55,6 +55,9 @@ static struct osmo_tdef tdefs_test[] = {
{ .T=4, .default_val=100, .unit=OSMO_TDEF_CUSTOM, .desc="Testing a hundred potatoes" },
{ .T=0x7fffffff, .default_val=0xffffffff, .unit=OSMO_TDEF_M, .desc="Very large" },
{ .T=-23, .default_val=239471, .desc="Negative T number" },
+ { .T=30, .default_val=50, .desc="Testing range min", .min_val=20 },
+ { .T=31, .default_val=50, .desc="Testing range max", .max_val=52 },
+ { .T=32, .default_val=50, .desc="Testing range both", .min_val=20, .max_val=52 },
{} // <-- important! last entry shall be zero
};
diff --git a/tests/tdef/tdef_vty_test_config_root.vty b/tests/tdef/tdef_vty_test_config_root.vty
index f3aba0f9..6a53b805 100644
--- a/tests/tdef/tdef_vty_test_config_root.vty
+++ b/tests/tdef/tdef_vty_test_config_root.vty
@@ -22,6 +22,9 @@ test: T3 = 100 m Testing a hundred minutes (default: 100 m)
test: T4 = 100 Testing a hundred potatoes (default: 100)
test: T2147483647 = 4294967295 m Very large (default: 4294967295 m)
test: X23 = 239471 s Negative T number (default: 239471 s)
+test: T30 = 50 s Testing range min (default: 50 s, range: [20 .. inf])
+test: T31 = 50 s Testing range max (default: 50 s, range: [0 .. 52])
+test: T32 = 50 s Testing range both (default: 50 s, range: [20 .. 52])
software: T1 = 30 m Write code (default: 30 m)
software: T2 = 20 ms Hit segfault (default: 20 ms)
software: T3 = 480 m Fix bugs (default: 480 m)
@@ -38,6 +41,9 @@ test: T3 = 100 m Testing a hundred minutes (default: 100 m)
test: T4 = 100 Testing a hundred potatoes (default: 100)
test: T2147483647 = 4294967295 m Very large (default: 4294967295 m)
test: X23 = 239471 s Negative T number (default: 239471 s)
+test: T30 = 50 s Testing range min (default: 50 s, range: [20 .. inf])
+test: T31 = 50 s Testing range max (default: 50 s, range: [0 .. 52])
+test: T32 = 50 s Testing range both (default: 50 s, range: [20 .. 52])
software: T1 = 30 m Write code (default: 30 m)
software: T2 = 20 ms Hit segfault (default: 20 ms)
software: T3 = 480 m Fix bugs (default: 480 m)
@@ -83,6 +89,9 @@ test: T3 = 100 m Testing a hundred minutes (default: 100 m)
test: T4 = 100 Testing a hundred potatoes (default: 100)
test: T2147483647 = 4294967295 m Very large (default: 4294967295 m)
test: X23 = 239471 s Negative T number (default: 239471 s)
+test: T30 = 50 s Testing range min (default: 50 s, range: [20 .. inf])
+test: T31 = 50 s Testing range max (default: 50 s, range: [0 .. 52])
+test: T32 = 50 s Testing range both (default: 50 s, range: [20 .. 52])
software: T1 = 30 m Write code (default: 30 m)
software: T2 = 20 ms Hit segfault (default: 20 ms)
software: T3 = 480 m Fix bugs (default: 480 m)
@@ -167,6 +176,32 @@ tdef_vty_test(config)# timer tes T2 100
tdef_vty_test(config)# timer te T2 100
% Ambiguous command.
+tdef_vty_test(config)# timer test 30 40
+tdef_vty_test(config)# timer test 30 60
+tdef_vty_test(config)# timer test 30 10
+% Timer T30 value 10 is out of range [20 .. inf]
+
+tdef_vty_test(config)# timer test 31 40
+tdef_vty_test(config)# timer test 31 60
+% Timer T31 value 60 is out of range [0 .. 52]
+tdef_vty_test(config)# timer test 31 10
+
+tdef_vty_test(config)# timer test 32 40
+tdef_vty_test(config)# timer test 32 60
+% Timer T32 value 60 is out of range [20 .. 52]
+tdef_vty_test(config)# timer test 32 10
+% Timer T32 value 10 is out of range [20 .. 52]
+
+tdef_vty_test(config)# timer test
+test: T1 = 100 s Testing a hundred seconds (default: 100 s)
+test: T2 = 100 ms Testing a hundred milliseconds (default: 100 ms)
+test: T3 = 100 m Testing a hundred minutes (default: 100 m)
+test: T4 = 100 Testing a hundred potatoes (default: 100)
+test: T2147483647 = 4294967295 m Very large (default: 4294967295 m)
+test: X23 = 239471 s Negative T number (default: 239471 s)
+test: T30 = 60 s Testing range min (default: 50 s, range: [20 .. inf])
+test: T31 = 10 s Testing range max (default: 50 s, range: [0 .. 52])
+test: T32 = 40 s Testing range both (default: 50 s, range: [20 .. 52])
tdef_vty_test(config)# do show timer software
software: T1 = 30 m Write code (default: 30 m)
@@ -230,6 +265,9 @@ test: T3 = 100 m Testing a hundred minutes (default: 100 m)
test: T4 = 100 Testing a hundred potatoes (default: 100)
test: T2147483647 = 4294967295 m Very large (default: 4294967295 m)
test: X23 = 239471 s Negative T number (default: 239471 s)
+test: T30 = 60 s Testing range min (default: 50 s, range: [20 .. inf])
+test: T31 = 10 s Testing range max (default: 50 s, range: [0 .. 52])
+test: T32 = 40 s Testing range both (default: 50 s, range: [20 .. 52])
software: T1 = 13 m Write code (default: 30 m)
software: T2 = 0 ms Hit segfault (default: 20 ms)
software: T3 = 23 m Fix bugs (default: 480 m)
@@ -245,6 +283,9 @@ test: T3 = 100 m Testing a hundred minutes (default: 100 m)
test: T4 = 100 Testing a hundred potatoes (default: 100)
test: T2147483647 = 4294967295 m Very large (default: 4294967295 m)
test: X23 = 239471 s Negative T number (default: 239471 s)
+test: T30 = 60 s Testing range min (default: 50 s, range: [20 .. inf])
+test: T31 = 10 s Testing range max (default: 50 s, range: [0 .. 52])
+test: T32 = 40 s Testing range both (default: 50 s, range: [20 .. 52])
software: T1 = 13 m Write code (default: 30 m)
software: T2 = 0 ms Hit segfault (default: 20 ms)
software: T3 = 23 m Fix bugs (default: 480 m)
@@ -252,6 +293,9 @@ software: T3 = 23 m Fix bugs (default: 480 m)
tdef_vty_test(config)# show running-config
... !timer
timer tea T3 32
+timer test T30 60
+timer test T31 10
+timer test T32 40
timer software T1 13
timer software T2 0
timer software T3 23
@@ -261,6 +305,9 @@ tdef_vty_test(config)# timer tea T3 default
tdef_vty_test(config)# timer software T1 default
tdef_vty_test(config)# show running-config
... !timer
+timer test T30 60
+timer test T31 10
+timer test T32 40
timer software T2 0
timer software T3 23
... !timer
@@ -269,5 +316,8 @@ tdef_vty_test(config)# timer softw 2 default
tdef_vty_test(config)# timer software 2 default
tdef_vty_test(config)# show running-config
... !timer
+timer test T30 60
+timer test T31 10
+timer test T32 40
timer software T3 23
... !timer
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 0fc96467..c231b964 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -126,6 +126,12 @@ cat $abs_srcdir/lapd/lapd_test.ok > expout
AT_CHECK([$abs_top_builddir/tests/lapd/lapd_test], [0], [expout], [ignore])
AT_CLEANUP
+AT_SETUP([gsm0502])
+AT_KEYWORDS([gsm0502])
+cat $abs_srcdir/gsm0502/gsm0502_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/gsm0502/gsm0502_test], [0], [expout], [ignore])
+AT_CLEANUP
+
AT_SETUP([gsm0808])
AT_KEYWORDS([gsm0808])
cat $abs_srcdir/gsm0808/gsm0808_test.ok > expout
@@ -350,3 +356,9 @@ cat $abs_srcdir/use_count/use_count_test.ok > expout
cat $abs_srcdir/use_count/use_count_test.err > experr
AT_CHECK([$abs_top_builddir/tests/use_count/use_count_test], [0], [expout], [experr])
AT_CLEANUP
+
+AT_SETUP([context])
+AT_KEYWORDS([context])
+cat $abs_srcdir/context/context_test.ok > expout
+AT_CHECK([$abs_top_builddir/tests/context/context_test], [0], [expout], [ignore])
+AT_CLEANUP
diff --git a/tests/vty/fail_cmd_ret_warning.cfg b/tests/vty/fail_cmd_ret_warning.cfg
new file mode 100644
index 00000000..b03f02ae
--- /dev/null
+++ b/tests/vty/fail_cmd_ret_warning.cfg
@@ -0,0 +1,3 @@
+return-success
+return-warning
+return-success
diff --git a/tests/vty/ok_deprecated_logging.cfg b/tests/vty/ok_deprecated_logging.cfg
new file mode 100644
index 00000000..2699719e
--- /dev/null
+++ b/tests/vty/ok_deprecated_logging.cfg
@@ -0,0 +1,3 @@
+log stderr
+ logging filter all 1
+ logging level depr debug
diff --git a/tests/vty/vty_test.c b/tests/vty/vty_test.c
index 30efb9af..1139638d 100644
--- a/tests/vty/vty_test.c
+++ b/tests/vty/vty_test.c
@@ -29,6 +29,7 @@
#include <osmocom/core/application.h>
#include <osmocom/core/talloc.h>
+#include <osmocom/core/logging_internal.h>
#include <osmocom/core/logging.h>
#include <osmocom/core/stats.h>
#include <osmocom/core/utils.h>
@@ -421,8 +422,29 @@ DEFUN(cfg_ambiguous_str_2, cfg_ambiguous_str_2_cmd,
return CMD_SUCCESS;
}
+DEFUN(cfg_ret_success, cfg_ret_success_cmd,
+ "return-success",
+ "testing return success\n")
+{
+ printf("Called: 'return-success'\n");
+ return CMD_SUCCESS;
+}
+
+DEFUN(cfg_ret_warning, cfg_ret_warning_cmd,
+ "return-warning",
+ "testing return warning\n")
+{
+ printf("Called: 'return-warning'\n");
+ return CMD_WARNING;
+}
+
void test_vty_add_cmds()
{
+ install_element(CONFIG_NODE, &cfg_ret_warning_cmd);
+ install_element(CONFIG_NODE, &cfg_ret_success_cmd);
+
+ logging_vty_add_deprecated_subsys(tall_log_ctx, "depr");
+
install_element(CONFIG_NODE, &cfg_level1_cmd);
install_node(&level1_node, NULL);
install_element(LEVEL1_NODE, &cfg_level1_child_cmd);
@@ -524,6 +546,8 @@ int main(int argc, char **argv)
test_exit_by_indent("fail_tabs_and_spaces.cfg", -EINVAL);
test_exit_by_indent("ok_indented_root.cfg", 0);
test_exit_by_indent("ok_empty_parent.cfg", 0);
+ test_exit_by_indent("fail_cmd_ret_warning.cfg", -EINVAL);
+ test_exit_by_indent("ok_deprecated_logging.cfg", 0);
test_is_cmd_ambiguous();
diff --git a/tests/vty/vty_test.ok b/tests/vty/vty_test.ok
index 2f76ff91..d2c96111 100644
--- a/tests/vty/vty_test.ok
+++ b/tests/vty/vty_test.ok
@@ -286,6 +286,12 @@ called level2 node k
called level3 node k
called level1 child cmd k
got rc=0
+reading file fail_cmd_ret_warning.cfg, expecting rc=-22
+Called: 'return-success'
+Called: 'return-warning'
+got rc=-22
+reading file ok_deprecated_logging.cfg, expecting rc=0
+got rc=0
Going to test is_cmd_ambiguous()
Going to execute 'ambiguous_nr'
Called: 'ambiguous_nr [<0-23>]' (argc=0)
diff --git a/utils/Makefile.am b/utils/Makefile.am
index fb791906..653b7190 100644
--- a/utils/Makefile.am
+++ b/utils/Makefile.am
@@ -1,7 +1,7 @@
if ENABLE_UTILITIES
AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include $(TALLOC_CFLAGS)
-AM_CFLAGS = -Wall
-LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gsm/libosmogsm.la
+AM_CFLAGS = -Wall $(PTHREAD_CFLAGS)
+LDADD = $(top_builddir)/src/libosmocore.la $(top_builddir)/src/gsm/libosmogsm.la $(PTHREAD_LIBS)
EXTRA_DIST = conv_gen.py conv_codes_gsm.py