Change in libosmocore[master]: add osmo_use_count API

Neels Hofmeyr gerrit-no-reply at lists.osmocom.org
Mon Mar 4 22:10:17 UTC 2019


Neels Hofmeyr has uploaded this change for review. ( https://gerrit.osmocom.org/13121


Change subject: add osmo_use_count API
......................................................................

add osmo_use_count API

Provide a common implementation of use counting that supports naming each user
as well as counting more than just one use per user, depending on the rules the
caller implies.

In osmo-msc, we were originally using a simple int counter to see whether a
connection is still in use or should be discarded. For clarity, we later added
names to each user in the form of a bitmask of flags, to figure out exactly
which users are still active: for logging and to debug double get / double put
bugs. This however is still not adequate, since there may be more than one CM
Service Request pending. Also, it is a specialized implementation that is not
re-usable.

With this generalized implementation, we can:

- fix the problem of inadequate counting of multiple concurrent CM Service
  Requests,
- add more than one use count per user category,
- use arbitrary names for users instead of enum definitions.

Change-Id: Ife31e6798b4e728a23913179e346552a7dd338c0
---
M include/Makefile.am
A include/osmocom/core/use_count.h
M src/Makefile.am
A src/use_count.c
M tests/Makefile.am
M tests/testsuite.at
A tests/use_count/use_count_test.c
A tests/use_count/use_count_test.err
A tests/use_count/use_count_test.ok
9 files changed, 980 insertions(+), 1 deletion(-)



  git pull ssh://gerrit.osmocom.org:29418/libosmocore refs/changes/21/13121/1

diff --git a/include/Makefile.am b/include/Makefile.am
index 17f7d1c..6f64fa6 100644
--- a/include/Makefile.am
+++ b/include/Makefile.am
@@ -52,6 +52,7 @@
                        osmocom/core/timer_compat.h \
                        osmocom/core/utils.h \
                        osmocom/core/write_queue.h \
+                       osmocom/core/use_count.h \
                        osmocom/crypt/auth.h \
                        osmocom/crypt/gprs_cipher.h \
 		       osmocom/ctrl/control_cmd.h \
diff --git a/include/osmocom/core/use_count.h b/include/osmocom/core/use_count.h
new file mode 100644
index 0000000..0c22fd4
--- /dev/null
+++ b/include/osmocom/core/use_count.h
@@ -0,0 +1,176 @@
+/*! \file use_count.h
+ * Generic object usage counter API (get, put and deallocate on zero count).
+ */
+/*
+ * (C) 2019 by sysmocom s.f.m.c. GmbH <info at sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels at hofmeyr.de>
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <osmocom/core/linuxlist.h>
+
+/*! \defgroup use_count  Use Counter
+ * @{
+ * \file use_count.h
+ */
+
+struct osmo_use_count_entry;
+
+/*! Invoked when a use count changes.
+ *
+ * The implementation is free to trigger actions on arbitrary use count changes, typically to free the
+ * use_count->talloc_object when the total use count reaches zero.
+ *
+ * The implementation may modify use_count_entry->count, for example for handling of get()/put() bugs, to clamp specific use
+ * tokens to specific counts, or to prevent the caller from put()ting into negative counts. When returning an error,
+ * there is no implicit undo -- if errors need to be corrected, this function is responsible for that.
+ *
+ * Be aware: use token strings are not copied, and use count entries usually remain listed also when they reach a zero
+ * count. This is trivially perfectly ok when using string literals as use tokens. It is also possible to use
+ * dynamically allocated string tokens, but should a use token string become invalid memory when reaching zero count, it
+ * is the responsibility of this function to set the use_count_entry->use = NULL; this is required to avoid subsequent
+ * osmo_use_count_get_put() invocations from calling strcmp() on invalid memory. (Setting use = NULL cannot be done
+ * implicitly after this callback invocation, because callback implementations are allowed to completely deallocate the
+ * talloc_object and the use_count list entries.)
+ *
+ * \param[in] use_count_entry  Use count entry that is being modified.
+ * \param[in] old_use_count  Use count the item had before the change in use count.
+ * \param[in] file  Source file string, passed in as __FILE__ from macro osmo_use_count_get_put().
+ * \param[in] line  Source file line, passed in as __LINE__ from macro osmo_use_count_get_put().
+ * \return 0 on success, negative if any undesired use count is reached; this rc will be returned by
+ *         osmo_use_count_get_put().
+ */
+typedef int (* osmo_use_count_cb_t )(struct osmo_use_count_entry *use_count_entry, int32_t old_use_count,
+				     const char *file, int line);
+
+/*! Use counter state for one used object.
+ *
+ * On initialization, typically, set a use_cb and a talloc_object.
+ *
+ * The talloc_object is typically a pointer to the object that this struct is a member of.
+ *
+ * The use_cb implementation allows to trigger actions when reaching specific use counts, e.g. deallocate when reaching
+ * a total of zero.
+ *
+ * Can be left fully zero initialized (the llist_head use_counts is implicitly initialized upon the first
+ * osmo_use_count_get_put()).
+ *
+ *     struct foo {
+ *             struct osmo_use_count use_count;
+ *     };
+ *
+ *     // Convenience macros for struct foo instances. These are strict about use count errors.
+ *     #define foo_get(FOO, USE) OSMO_ASSERT( osmo_use_count_get_put(&(FOO)->use_count, USE, 1) == 0 );
+ *     #define foo_put(FOO, USE) OSMO_ASSERT( osmo_use_count_get_put(&(FOO)->use_count, USE, -1) == 0 );
+ *
+ *     int foo_use_cb(struct osmo_use_count_entry *use_count_entry, int32_t old_use_count, const char *file, int line)
+ *     {
+ *             struct foo *foo = use_count_entry->use_count->talloc_object;
+ *             if (osmo_use_count_total(&use_count_entry->use_count) == 0)
+ *                     talloc_free(foo);
+ *     }
+ *
+ *     // The function name is a convenient use token:
+ *     void rx_stop_baz_request(struct foo *foo)
+ *     {
+ *             foo_get(foo, __func__);
+ *
+ *             foo_put(foo, "baz");
+ *             printf("Stopped Bazing (%p)\n", foo);
+ *
+ *             foo_put(foo, __func__);
+ *     }
+ *
+ *     void use_count_example()
+ *     {
+ *             struct foo *foo = talloc_zero(ctx, struct foo);
+ *             *foo = (struct foo){
+ *                     .use_count = {
+ *                             .talloc_object = foo,
+ *                             .use_cb = foo_use_cb,
+ *                     },
+ *             };
+ *
+ *             foo_get(foo, "bar");
+ *             foo_get(foo, "baz");
+ *             foo_get(foo, "baz");
+ *
+ *             printf("use: %s\n", osmo_use_count_name_buf(namebuf, sizeof(namebuf), &foo->use_count));
+ *             // "use: 3 (bar,2*baz)"
+ *
+ *             foo_put(foo, "bar");
+ *             foo_put(foo, "baz");
+ *             rx_stop_baz_request(foo);
+ *             // freed.
+ *     };
+ */
+struct osmo_use_count {
+	/*! Context to talloc use count entries from, as well as back-pointer to the owning object for
+	 * osmo_use_count_cb_t implementations. */
+	void *talloc_object;
+	/*! If not NULL, this is invoked for each use count change. */
+	osmo_use_count_cb_t use_cb;
+	/*! List of use tokens. No need to touch this, the llist is initialized implicitly. */
+	struct llist_head use_counts;
+};
+
+/*! Entry for a single use token. Gets created as necessary by osmo_use_count_get_put().
+ */
+struct osmo_use_count_entry {
+	/*! Entry in osmo_use_count::use_counts. */
+	struct llist_head entry;
+	/*! Parent use count and backpointer to the talloc_object. */
+	struct osmo_use_count *use_count;
+	/*! Use token string that was passed to osmo_use_count_get_put(). */
+	const char *use;
+	/*! Current use count. Can be negative, if the use_cb implementation permits that. */
+	int32_t count;
+};
+
+/*! Change the use count for a given use token.
+ * \param USE_LIST  A struct osmo_use_count*, e.g. &my_obj->use_count.
+ * \param USE  A use token: arbitrary string (const char*). This must remain valid memory, e.g. string constants.
+ * \param CHANGE  Signed integer value to add to the use count: positive means get(), negative means put().
+ * \return Negative on range violations or USE_LIST == NULL, the use_cb()'s return value, or 0 on success.
+ */
+#define osmo_use_count_get_put(USE_LIST, USE, CHANGE) \
+	_osmo_use_count_get_put(USE_LIST, USE, CHANGE, __FILE__, __LINE__)
+
+int _osmo_use_count_get_put(struct osmo_use_count *uc, const char *use, int32_t diff,
+			    const char *file, int line);
+
+const char *osmo_use_count_name_buf(char *buf, size_t buf_len, const struct osmo_use_count *uc);
+
+int32_t osmo_use_count_total(const struct osmo_use_count *uc);
+int32_t osmo_use_count_by(const struct osmo_use_count *uc, const char *use);
+
+struct osmo_use_count_entry *osmo_use_count_find(const struct osmo_use_count *uc, const char *use);
+void osmo_use_count_free(struct osmo_use_count_entry *use_count_entry);
+
+void osmo_use_count_make_static_entries(struct osmo_use_count *uc, struct osmo_use_count_entry *buf,
+					size_t buf_n_entries);
+
+/*! @} */
diff --git a/src/Makefile.am b/src/Makefile.am
index 27ab702..726ad15 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -24,7 +24,9 @@
 			 macaddr.c stat_item.c stats.c stats_statsd.c prim.c \
 			 conv_acc.c conv_acc_generic.c sercomm.c prbs.c \
 			 isdnhdlc.c \
-			 tdef.c
+			 tdef.c \
+			 use_count.c \
+			 $(NULL)
 
 if HAVE_SSSE3
 libosmocore_la_SOURCES += conv_acc_sse.c
diff --git a/src/use_count.c b/src/use_count.c
new file mode 100644
index 0000000..4de0128
--- /dev/null
+++ b/src/use_count.c
@@ -0,0 +1,278 @@
+/*! \file use_count.c
+ * Generic object usage counter Implementation (get, put and deallocate on zero count).
+ */
+/*
+ * (C) 2019 by sysmocom s.f.m.c. GmbH <info at sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels at hofmeyr.de>
+ *
+ * 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 <errno.h>
+#include <inttypes.h>
+#include <string.h>
+
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/use_count.h>
+
+/*! \addtogroup use_count
+ *
+ * Generic object usage counter (get, put and deallocate on zero count).
+ *
+ * For an example, see struct osmo_use_count.
+ *
+ * @{
+ * \file use_count.c
+ */
+
+/*! Add two int32_t but make sure to min- and max-clamp at INT32_MIN and INT32_MAX, respectively. */
+static inline bool count_safe(int32_t *val_p, int32_t add)
+{
+	int32_t val = *val_p;
+
+	/* A simpler implementation would just let the integer overflow and compare with previous value afterwards, but
+	 * that causes runtime errors in the address sanitizer. So let's just do this without tricks. */
+	if (add < 0 && val < 0 && val - INT32_MIN < -add) {
+		*val_p = INT32_MIN;
+		return false;
+	}
+
+	if (add > 0 && val > 0 && INT32_MAX - val < add) {
+		*val_p = INT32_MAX;
+		return false;
+	}
+
+	*val_p = val + add;
+	return true;
+}
+
+/*! Return the sum of all use counts, min- and max-clamped at INT32_MIN and INT32_MAX.
+ * \param[in] uc  Use counts to sum up.
+ * \return Accumulated counts, or 0 if uc is NULL.
+ */
+int32_t osmo_use_count_total(const struct osmo_use_count *uc)
+{
+	struct osmo_use_count_entry *e;
+	int32_t total = 0;
+
+	if (!uc || !uc->use_counts.next)
+		return 0;
+
+	llist_for_each_entry(e, &uc->use_counts, entry) {
+		count_safe(&total, e->count);
+	}
+	return total;
+}
+
+/*! Return use count by a single use token.
+ * \param[in] uc  Use counts to look up in.
+ * \param[in] use  Use token.
+ * \return Use count, or 0 if uc is NULL or use token is not present.
+ */
+int32_t osmo_use_count_by(const struct osmo_use_count *uc, const char *use)
+{
+	const struct osmo_use_count_entry *e;
+	if (!uc)
+		return 0;
+	e = osmo_use_count_find(uc, use);
+	if (!e)
+		return 0;
+	return e->count;
+}
+
+/*! Write a comprehensive listing of use counts to a string buffer.
+ * Reads like "12 (3*barring,fighting,8*kungfoo)".
+ * \param[inout] buf  Destination buffer.
+ * \param[in] buf_len  sizeof(buf).
+ * \param[in] uc  Use counts to print.
+ * \return buf, always nul-terminated (except when buf_len < 1).
+ */
+const char *osmo_use_count_name_buf(char *buf, size_t buf_len, const struct osmo_use_count *uc)
+{
+	int32_t count = osmo_use_count_total(uc);
+	struct osmo_strbuf sb = { .buf = buf, .len = buf_len };
+	struct osmo_use_count_entry *e;
+	bool first;
+
+	OSMO_STRBUF_PRINTF(sb, "%" PRId32 " (", count);
+
+	first = true;
+	llist_for_each_entry(e, &uc->use_counts, entry) {
+		if (!e->count)
+			continue;
+		if (!first)
+			OSMO_STRBUF_PRINTF(sb, ",");
+		first = false;
+		if (e->count != 1)
+			OSMO_STRBUF_PRINTF(sb, "%" PRId32 "*", e->count);
+		OSMO_STRBUF_PRINTF(sb, "%s", e->use ? : "NULL");
+	}
+	if (first)
+		OSMO_STRBUF_PRINTF(sb, "-");
+	OSMO_STRBUF_PRINTF(sb, ")");
+	return buf;
+}
+
+/* Return a use token's use count entry.
+ * \param[in] uc  Use counts to look up in.
+ * \param[in] use  Use token.
+ * \return matching entry, or NULL if not present.
+ */
+struct osmo_use_count_entry *osmo_use_count_find(const struct osmo_use_count *uc, const char *use)
+{
+	struct osmo_use_count_entry *e;
+	if (!uc->use_counts.next)
+		return NULL;
+	llist_for_each_entry(e, &uc->use_counts, entry) {
+		if (e->use == use || (use && e->use && !strcmp(e->use, use)))
+			return e;
+	}
+	return NULL;
+}
+
+/*! Find a use count entry that currently has zero count, and re-use that for this new use token. */
+static struct osmo_use_count_entry *osmo_use_count_repurpose_zero_entry(struct osmo_use_count *uc, const char *use)
+{
+	struct osmo_use_count_entry *e;
+	if (!uc->use_counts.next)
+		return NULL;
+	llist_for_each_entry(e, &uc->use_counts, entry) {
+		if (!e->count) {
+			e->use = use;
+			return e;
+		}
+	}
+	return NULL;
+}
+
+/*! Allocate a new use count entry, happens implicitly in osmo_use_count_get_put(). */
+static struct osmo_use_count_entry *osmo_use_count_create(struct osmo_use_count *uc, const char *use)
+{
+	struct osmo_use_count_entry *e = talloc_zero(uc->talloc_object, struct osmo_use_count_entry);
+	if (!e)
+		return NULL;
+	*e = (struct osmo_use_count_entry){
+		.use_count = uc,
+		.use = use,
+	};
+	if (!uc->use_counts.next)
+		INIT_LLIST_HEAD(&uc->use_counts);
+	llist_add_tail(&e->entry, &uc->use_counts);
+	return e;
+}
+
+/*! Deallocate a use count entry.
+ * Normally, this is not necessary -- it is ok and even desirable to leave use count entries around even when they reach
+ * a count of zero, until the use_count->talloc_object deallocates and removes all of them in one flush. This avoids
+ * repeated allocation and deallocation for use tokens, because use count entries that have reached zero count are
+ * repurposed for any other use tokens. A cleanup makes sense only if a large number of concurrent uses of the same
+ * object has created as many use count entries; if so, this should be done by the osmo_use_count_cb_t implementation.
+ *
+ * osmo_use_count_free() must *not* be called on use count entries that were added by
+ * osmo_use_count_make_static_entries().
+ *
+ * \param[in] use_count_entry  Use count entry to unlist and free.
+ */
+void osmo_use_count_free(struct osmo_use_count_entry *use_count_entry)
+{
+	if (!use_count_entry)
+		return;
+	llist_del(&use_count_entry->entry);
+	talloc_free(use_count_entry);
+}
+
+/*! Implementation for osmo_use_count_get_put(), which can also be directly invoked to pass source file information. For
+ * arguments besides file and line, see osmo_use_count_get_put().
+ * \param[in] file  Source file path, as in __FILE__.
+ * \param[in] line  Source file line, as in __LINE__.
+ */
+int _osmo_use_count_get_put(struct osmo_use_count *uc, const char *use, int32_t change,
+			    const char *file, int line)
+{
+	struct osmo_use_count_entry *e;
+	int32_t old_use_count;
+	if (!change)
+		return 0;
+
+	e = osmo_use_count_find(uc, use);
+	if (!e)
+		e = osmo_use_count_repurpose_zero_entry(uc, use);
+	if (!e)
+		e = osmo_use_count_create(uc, use);
+	if (!e)
+		return -ENOMEM;
+
+	if (!e->count) {
+		/* move to end */
+		llist_del(&e->entry);
+		llist_add_tail(&e->entry, &uc->use_counts);
+	}
+
+	old_use_count = e->count;
+	if (!count_safe(&e->count, change)) {
+		e->count = old_use_count;
+		return -ERANGE;
+	}
+
+	if (uc->use_cb)
+		return uc->use_cb(e, old_use_count, file, line);
+	return 0;
+}
+
+/*! Add N static use token entries to avoid dynamic allocation of use count tokens.
+ * When not using this function, use count entries are talloc allocated from uc->talloc_object as ctx. This means that
+ * there are small dynamic allocations for each use count. osmo_use_count_get_put() normally leaves zero-count entries
+ * around and re-purposes them later, so the number of small allocations is at most the number of concurrent uses of the
+ * same object -- fair enough. If that is not enough, this function allows completely avoiding dynamic use count
+ * allocations, by adding N static entries with a zero count and a NULL use token. They will be used by
+ * osmo_use_count_get_put(), and, if the caller avoids using osmo_use_count_free(), the osmo_use_count implementation
+ * never deallocates them. The idea is that the entries are members of the uc->talloc_object, or that they will by other
+ * means be implicitly deallocated by the talloc_object. It is fine to call
+ * osmo_use_count_make_static_entries(buf_n_entries=N) and later have more than N concurrent uses, i.e. it is no
+ * problem to mix static and dynamic entries. To completely avoid dynamic use count entries, N has to >= the maximum
+ * number of concurrent users with differing use tokens that will occur in the lifetime of the talloc_object.
+ *
+ *    struct my_object {
+ *            struct osmo_use_count use_count;
+ *            struct osmo_use_count_entry use_count_buf[3]; // planning for 3 concurrent users
+ *    };
+ *
+ *    void example() {
+ *            struct my_object *o = talloc_zero(ctx, struct my_object);
+ *            osmo_use_count_make_static_entries(&o->use_count, o->use_count_buf, ARRAY_SIZE(o->use_count_buf));
+ *    }
+ */
+void osmo_use_count_make_static_entries(struct osmo_use_count *uc, struct osmo_use_count_entry *buf,
+					size_t buf_n_entries)
+{
+	size_t idx;
+	if (!uc->use_counts.next)
+		INIT_LLIST_HEAD(&uc->use_counts);
+	for (idx = 0; idx < buf_n_entries; idx++) {
+		struct osmo_use_count_entry *e = &buf[idx];
+		*e = (struct osmo_use_count_entry){
+			.use_count = uc,
+		};
+		llist_add_tail(&e->entry, &uc->use_counts);
+	}
+}
+
+/*! @} */
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 63f3959..0584834 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -30,6 +30,7 @@
 		 tdef/tdef_test tdef/tdef_vty_test_config_root		\
 		 tdef/tdef_vty_test_config_subnode			\
 		 tdef/tdef_vty_test_dynamic				\
+		 use_count/use_count_test				\
 		 $(NULL)
 
 if ENABLE_MSGFILE
@@ -236,6 +237,9 @@
 tdef_tdef_vty_test_dynamic_SOURCES = tdef/tdef_vty_test_dynamic.c
 tdef_tdef_vty_test_dynamic_LDADD = $(LDADD) $(top_builddir)/src/vty/libosmovty.la
 
+use_count_use_count_test_SOURCES = use_count/use_count_test.c
+use_count_use_count_test_LDADD = $(LDADD)
+
 # The `:;' works around a Bash 3.2 bug when the output is not writeable.
 $(srcdir)/package.m4: $(top_srcdir)/configure.ac
 	:;{ \
@@ -304,6 +308,7 @@
 	     tdef/tdef_vty_test_config_root.vty	\
 	     tdef/tdef_vty_test_config_subnode.vty \
 	     tdef/tdef_vty_test_dynamic.vty \
+	     use_count/use_count_test.ok use_count/use_count_test.err \
 	     $(NULL)
 
 DISTCLEANFILES = atconfig atlocal conv/gsm0503_test_vectors.c
diff --git a/tests/testsuite.at b/tests/testsuite.at
index 0093403..edf127d 100644
--- a/tests/testsuite.at
+++ b/tests/testsuite.at
@@ -331,3 +331,10 @@
 cat $abs_srcdir/tdef/tdef_test.ok > expout
 AT_CHECK([$abs_top_builddir/tests/tdef/tdef_test], [0], [expout], [ignore])
 AT_CLEANUP
+
+AT_SETUP([use_count])
+AT_KEYWORDS([use_count])
+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
diff --git a/tests/use_count/use_count_test.c b/tests/use_count/use_count_test.c
new file mode 100644
index 0000000..0b081c9
--- /dev/null
+++ b/tests/use_count/use_count_test.c
@@ -0,0 +1,339 @@
+/* Test implementation for osmo_use_count API. */
+/*
+ * (C) 2019 by sysmocom s.f.m.c. GmbH <info at sysmocom.de>
+ *
+ * All Rights Reserved
+ *
+ * Author: Neels Hofmeyr <neels at hofmeyr.de>
+ *
+ * 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 <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <limits.h>
+
+#include <osmocom/core/logging.h>
+#include <osmocom/core/application.h>
+#include <osmocom/core/fsm.h>
+
+#include <osmocom/core/use_count.h>
+
+static void *ctx = NULL;
+
+#define log(fmt, args...) fprintf(stderr, fmt, ##args)
+
+enum {
+	DFOO,
+};
+
+#define FOO_USE_BARRING "barring"
+#define FOO_USE_FIGHTING "fighting"
+#define FOO_USE_KUNG "kungfoo"
+#define FOO_USE_RELEASING "releasing"
+
+LLIST_HEAD(all_foo);
+
+struct foo {
+	struct llist_head entry;
+	struct osmo_fsm_inst *fi;
+	struct osmo_use_count use_count;
+	struct osmo_use_count_entry use_count_buf[10];
+};
+
+enum foo_fsm_events {
+	FOO_EV_UNUSED,
+};
+
+static char name_buf[1024];
+#define use_count_name(UL) osmo_use_count_name_buf(name_buf, sizeof(name_buf), UL)
+
+int foo_use_cb(struct osmo_use_count_entry *use_count_entry, int32_t old_use_count,
+	       const char *file, int line)
+{
+	struct osmo_use_count *use_count = use_count_entry->use_count;
+	struct foo *foo = use_count->talloc_object;
+	const char *use = use_count_entry->use;
+	int32_t new_use_count = use_count_entry->count;
+
+	if (use && (!strcmp(use, FOO_USE_BARRING) || !strcmp(use, FOO_USE_RELEASING))
+	    && new_use_count > 1) {
+		LOGPFSMLSRC(foo->fi, LOGL_ERROR, file, line,
+			    "Attempt to get more than one %s\n", use);
+		/* Fix the use count */
+		use_count_entry->count = 1;
+		return -ERANGE;
+	}
+
+	LOGPFSMLSRC(foo->fi, LOGL_NOTICE, file, line, "%s %+d %s: now used by %s\n",
+		    foo->fi->id, new_use_count - old_use_count, use ? : "NULL", use_count_name(use_count));
+
+	if (new_use_count < 0) {
+		LOGPFSMLSRC(foo->fi, LOGL_ERROR, file, line, "Negative use count on %s: %s\n",
+			    use ? : "NULL", use_count_name(use_count));
+		/* Let it pass for the sake of this test */
+	}
+
+	if (osmo_use_count_total(use_count) == 0)
+		osmo_fsm_inst_dispatch(foo->fi, FOO_EV_UNUSED, NULL);
+	return 0;
+}
+
+#define foo_get_put(FOO, USE, CHANGE) do { \
+		int rc = osmo_use_count_get_put(&(FOO)->use_count, USE, CHANGE); \
+		if (rc) \
+			log("osmo_use_count_get_put(%s, %s, %d) returned error: %d %s\n", \
+			    (FOO)->fi->id, USE ? : "NULL", CHANGE, rc, strerror(-rc)); \
+	} while(0)
+
+#define foo_get(FOO, USE) foo_get_put(FOO, USE, 1)
+#define foo_put(FOO, USE) foo_get_put(FOO, USE, -1)
+
+enum foo_fsm_states {
+	FOO_ST_IN_USE,
+	FOO_ST_IN_RELEASE,
+};
+
+void foo_fsm_in_use(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	OSMO_ASSERT(event == FOO_EV_UNUSED);
+	osmo_fsm_inst_state_chg(fi, FOO_ST_IN_RELEASE, 0, 0);
+}
+
+void foo_fsm_in_release_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state)
+{
+	struct foo *foo = fi->priv;
+	foo_get(foo, FOO_USE_RELEASING);
+}
+
+void foo_fsm_in_release(struct osmo_fsm_inst *fi, uint32_t event, void *data)
+{
+	OSMO_ASSERT(event == FOO_EV_UNUSED);
+	osmo_fsm_inst_term(fi, OSMO_FSM_TERM_REGULAR, NULL);
+}
+
+
+#define S(x)	(1 << (x))
+
+static const struct osmo_fsm_state foo_fsm_states[] = {
+	[FOO_ST_IN_USE] = {
+		.name = "IN_USE",
+		.in_event_mask = 0
+			| S(FOO_EV_UNUSED)
+			,
+		.out_state_mask = 0
+			| S(FOO_ST_IN_RELEASE)
+			,
+		.action = foo_fsm_in_use,
+	},
+	[FOO_ST_IN_RELEASE] = {
+		.name = "IN_RELEASE",
+		.in_event_mask = 0
+			| S(FOO_EV_UNUSED)
+			,
+		.out_state_mask = 0
+			,
+		.onenter = foo_fsm_in_release_onenter,
+		.action = foo_fsm_in_release,
+	},
+};
+
+static const struct value_string foo_fsm_event_names[] = {
+	OSMO_VALUE_STRING(FOO_EV_UNUSED),
+	{}
+};
+
+void foo_cleanup(struct osmo_fsm_inst *fi, enum osmo_fsm_term_cause cause)
+{
+	struct foo *foo = fi->priv;
+	llist_del(&foo->entry);
+}
+
+static struct osmo_fsm foo_fsm = {
+	.name = "foo",
+	.states = foo_fsm_states,
+	.event_names = foo_fsm_event_names,
+	.num_states = ARRAY_SIZE(foo_fsm_states),
+	.log_subsys = DFOO,
+	.cleanup = foo_cleanup,
+};
+
+static struct foo *foo_alloc(const char *name, size_t static_entries)
+{
+	struct foo *foo;
+	struct osmo_fsm_inst *fi = osmo_fsm_inst_alloc(&foo_fsm, ctx, NULL, LOGL_DEBUG, name);
+	OSMO_ASSERT(fi);
+	OSMO_ASSERT(static_entries <= ARRAY_SIZE(foo->use_count_buf));
+
+	foo = talloc_zero(fi, struct foo);
+	*foo = (struct foo){
+		.fi = fi,
+		.use_count = {
+			.talloc_object = foo,
+			.use_cb = foo_use_cb,
+		},
+	};
+	fi->priv = foo;
+
+	osmo_use_count_make_static_entries(&foo->use_count, foo->use_count_buf, static_entries);
+
+	llist_add_tail(&foo->entry, &all_foo);
+	return foo;
+}
+
+void print_foos()
+{
+	int count = 0;
+	struct foo *foo;
+	fprintf(stderr, "\nall use counts:\n");
+	llist_for_each_entry(foo, &all_foo, entry) {
+		fprintf(stderr, "%s: %s\n", foo->fi->id, use_count_name(&foo->use_count));
+		count++;
+	}
+	fprintf(stderr, "%d foos\n\n", count);
+}
+
+static void test_use_count_fsm()
+{
+	struct foo *a, *b, *c;
+	log("\n%s()\n", __func__);
+
+	a = foo_alloc("a", 0);
+	b = foo_alloc("b", 2);
+	c = foo_alloc("c", 10);
+	print_foos();
+
+	log("A few gets and puts, logging source file information\n");
+	log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME);
+	foo_get(a, FOO_USE_BARRING);
+
+	foo_get(b, FOO_USE_BARRING);
+	foo_get(b, FOO_USE_FIGHTING);
+
+	print_foos();
+
+	log("Attempt to get more than one on limited 'barring' user:\n");
+	foo_get(b, FOO_USE_BARRING);
+	print_foos();
+
+	log("Put away one user of b\n");
+	foo_put(b, FOO_USE_BARRING);
+	print_foos();
+
+	log("(no longer log source file information)\n");
+	log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
+
+	log("Test null use token\n");
+	foo_get(a, NULL);
+	print_foos();
+	foo_put(a, NULL);
+	print_foos();
+
+	log("Put away last user of a, goes to RELEASING state and waits for a hypothetic async release process\n");
+	foo_put(a, FOO_USE_BARRING);
+	print_foos();
+
+	log("Async releasing of a is done, will dealloc\n");
+	foo_put(a, FOO_USE_RELEASING);
+	print_foos();
+
+	log("Use b multiple times\n");
+	foo_get(b, FOO_USE_KUNG);
+	foo_get(b, FOO_USE_KUNG);
+
+	foo_put(b, FOO_USE_KUNG);
+	foo_get(b, FOO_USE_KUNG);
+
+	foo_get(b, FOO_USE_KUNG);
+	print_foos();
+
+	log("Test range: set kung-fu to INT32_MAX-1, then get three more; total count gets max-clamped to INT32_MAX\n");
+	foo_get_put(b, FOO_USE_KUNG, INT32_MAX-1 - osmo_use_count_by(&b->use_count, FOO_USE_KUNG));
+	print_foos();
+	foo_get(b, FOO_USE_KUNG);
+	foo_get(b, FOO_USE_KUNG);
+	foo_get(b, FOO_USE_KUNG);
+	foo_get_put(b, FOO_USE_FIGHTING, 2);
+	foo_get_put(b, FOO_USE_KUNG, -3);
+	foo_put(b, FOO_USE_KUNG);
+	foo_put(b, FOO_USE_KUNG);
+	foo_get(b, FOO_USE_FIGHTING);
+	foo_get(b, FOO_USE_FIGHTING);
+	foo_get(b, FOO_USE_FIGHTING);
+	print_foos();
+
+	log("Release all uses of b\n");
+	foo_get_put(b, FOO_USE_KUNG, - osmo_use_count_by(&b->use_count, FOO_USE_KUNG));
+	foo_get_put(b, FOO_USE_FIGHTING, - osmo_use_count_by(&b->use_count, FOO_USE_FIGHTING));
+
+	log("Signal async release as done\n");
+	foo_put(b, FOO_USE_RELEASING);
+	print_foos();
+
+	log("Release something not gotten before: a get/put bug goes into negative count\n");
+	foo_put(c, FOO_USE_KUNG);
+	print_foos();
+	log("More negative\n");
+	foo_put(c, FOO_USE_KUNG);
+	foo_put(c, FOO_USE_KUNG);
+	print_foos();
+
+	log("Also release c\n");
+	foo_get_put(c, FOO_USE_KUNG, 4);
+	foo_put(c, FOO_USE_KUNG);
+	log("Signal async release as done\n");
+	foo_put(c, FOO_USE_RELEASING);
+	print_foos();
+}
+
+static const struct log_info_cat default_categories[] = {
+	[DFOO] = {
+		.name = "DFOO",
+		.description = "FOO",
+		.enabled = 1, .loglevel = LOGL_DEBUG,
+	},
+};
+
+static const struct log_info log_info = {
+	.cat = default_categories,
+	.num_cat = ARRAY_SIZE(default_categories),
+};
+
+
+int main(int argc, char **argv)
+{
+	ctx = talloc_named_const(NULL, 0, "use_count_test.c");
+
+	osmo_fsm_log_addr(false);
+
+	osmo_init_logging2(ctx, &log_info);
+
+	log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_BASENAME);
+	log_set_print_filename2(osmo_stderr_target, LOG_FILENAME_NONE);
+	log_set_print_filename_pos(osmo_stderr_target, LOG_FILENAME_POS_LINE_END);
+	log_set_print_category(osmo_stderr_target, 1);
+	log_set_print_category_hex(osmo_stderr_target, 0);
+	log_set_print_level(osmo_stderr_target, 1);
+	log_set_use_color(osmo_stderr_target, 0);
+
+	osmo_fsm_register(&foo_fsm);
+
+	test_use_count_fsm();
+
+	return EXIT_SUCCESS;
+}
diff --git a/tests/use_count/use_count_test.err b/tests/use_count/use_count_test.err
new file mode 100644
index 0000000..97e74a5
--- /dev/null
+++ b/tests/use_count/use_count_test.err
@@ -0,0 +1,171 @@
+
+test_use_count_fsm()
+DFOO DEBUG foo(a){IN_USE}: Allocated
+DFOO DEBUG foo(b){IN_USE}: Allocated
+DFOO DEBUG foo(c){IN_USE}: Allocated
+
+all use counts:
+a: 0 (-)
+b: 0 (-)
+c: 0 (-)
+3 foos
+
+A few gets and puts, logging source file information
+DFOO NOTICE foo(a){IN_USE}: a +1 barring: now used by 1 (barring) (use_count_test.c:223)
+DFOO NOTICE foo(b){IN_USE}: b +1 barring: now used by 1 (barring) (use_count_test.c:225)
+DFOO NOTICE foo(b){IN_USE}: b +1 fighting: now used by 2 (barring,fighting) (use_count_test.c:226)
+
+all use counts:
+a: 1 (barring)
+b: 2 (barring,fighting)
+c: 0 (-)
+3 foos
+
+Attempt to get more than one on limited 'barring' user:
+DFOO ERROR foo(b){IN_USE}: Attempt to get more than one barring (use_count_test.c:231)
+osmo_use_count_get_put(b, barring, 1) returned error: -34 Numerical result out of range
+
+all use counts:
+a: 1 (barring)
+b: 2 (barring,fighting)
+c: 0 (-)
+3 foos
+
+Put away one user of b
+DFOO NOTICE foo(b){IN_USE}: b -1 barring: now used by 1 (fighting) (use_count_test.c:235)
+
+all use counts:
+a: 1 (barring)
+b: 1 (fighting)
+c: 0 (-)
+3 foos
+
+(no longer log source file information)
+Test null use token
+DFOO NOTICE foo(a){IN_USE}: a +1 NULL: now used by 2 (barring,NULL)
+
+all use counts:
+a: 2 (barring,NULL)
+b: 1 (fighting)
+c: 0 (-)
+3 foos
+
+DFOO NOTICE foo(a){IN_USE}: a -1 NULL: now used by 1 (barring)
+
+all use counts:
+a: 1 (barring)
+b: 1 (fighting)
+c: 0 (-)
+3 foos
+
+Put away last user of a, goes to RELEASING state and waits for a hypothetic async release process
+DFOO NOTICE foo(a){IN_USE}: a -1 barring: now used by 0 (-)
+DFOO DEBUG foo(a){IN_USE}: Received Event FOO_EV_UNUSED
+DFOO DEBUG foo(a){IN_USE}: state_chg to IN_RELEASE
+DFOO NOTICE foo(a){IN_RELEASE}: a +1 releasing: now used by 1 (releasing)
+
+all use counts:
+a: 1 (releasing)
+b: 1 (fighting)
+c: 0 (-)
+3 foos
+
+Async releasing of a is done, will dealloc
+DFOO NOTICE foo(a){IN_RELEASE}: a -1 releasing: now used by 0 (-)
+DFOO DEBUG foo(a){IN_RELEASE}: Received Event FOO_EV_UNUSED
+DFOO DEBUG foo(a){IN_RELEASE}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DFOO DEBUG foo(a){IN_RELEASE}: Freeing instance
+DFOO DEBUG foo(a){IN_RELEASE}: Deallocated
+
+all use counts:
+b: 1 (fighting)
+c: 0 (-)
+2 foos
+
+Use b multiple times
+DFOO NOTICE foo(b){IN_USE}: b +1 kungfoo: now used by 2 (fighting,kungfoo)
+DFOO NOTICE foo(b){IN_USE}: b +1 kungfoo: now used by 3 (fighting,2*kungfoo)
+DFOO NOTICE foo(b){IN_USE}: b -1 kungfoo: now used by 2 (fighting,kungfoo)
+DFOO NOTICE foo(b){IN_USE}: b +1 kungfoo: now used by 3 (fighting,2*kungfoo)
+DFOO NOTICE foo(b){IN_USE}: b +1 kungfoo: now used by 4 (fighting,3*kungfoo)
+
+all use counts:
+b: 4 (fighting,3*kungfoo)
+c: 0 (-)
+2 foos
+
+Test range: set kung-fu to INT32_MAX-1, then get three more; total count gets max-clamped to INT32_MAX
+DFOO NOTICE foo(b){IN_USE}: b +2147483643 kungfoo: now used by 2147483647 (fighting,2147483646*kungfoo)
+
+all use counts:
+b: 2147483647 (fighting,2147483646*kungfoo)
+c: 0 (-)
+2 foos
+
+DFOO NOTICE foo(b){IN_USE}: b +1 kungfoo: now used by 2147483647 (fighting,2147483647*kungfoo)
+osmo_use_count_get_put(b, kungfoo, 1) returned error: -34 Numerical result out of range
+osmo_use_count_get_put(b, kungfoo, 1) returned error: -34 Numerical result out of range
+DFOO NOTICE foo(b){IN_USE}: b +2 fighting: now used by 2147483647 (3*fighting,2147483647*kungfoo)
+DFOO NOTICE foo(b){IN_USE}: b -3 kungfoo: now used by 2147483647 (3*fighting,2147483644*kungfoo)
+DFOO NOTICE foo(b){IN_USE}: b -1 kungfoo: now used by 2147483646 (3*fighting,2147483643*kungfoo)
+DFOO NOTICE foo(b){IN_USE}: b -1 kungfoo: now used by 2147483645 (3*fighting,2147483642*kungfoo)
+DFOO NOTICE foo(b){IN_USE}: b +1 fighting: now used by 2147483646 (4*fighting,2147483642*kungfoo)
+DFOO NOTICE foo(b){IN_USE}: b +1 fighting: now used by 2147483647 (5*fighting,2147483642*kungfoo)
+DFOO NOTICE foo(b){IN_USE}: b +1 fighting: now used by 2147483647 (6*fighting,2147483642*kungfoo)
+
+all use counts:
+b: 2147483647 (6*fighting,2147483642*kungfoo)
+c: 0 (-)
+2 foos
+
+Release all uses of b
+DFOO NOTICE foo(b){IN_USE}: b -2147483642 kungfoo: now used by 6 (6*fighting)
+DFOO NOTICE foo(b){IN_USE}: b -6 fighting: now used by 0 (-)
+DFOO DEBUG foo(b){IN_USE}: Received Event FOO_EV_UNUSED
+DFOO DEBUG foo(b){IN_USE}: state_chg to IN_RELEASE
+DFOO NOTICE foo(b){IN_RELEASE}: b +1 releasing: now used by 1 (releasing)
+Signal async release as done
+DFOO NOTICE foo(b){IN_RELEASE}: b -1 releasing: now used by 0 (-)
+DFOO DEBUG foo(b){IN_RELEASE}: Received Event FOO_EV_UNUSED
+DFOO DEBUG foo(b){IN_RELEASE}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DFOO DEBUG foo(b){IN_RELEASE}: Freeing instance
+DFOO DEBUG foo(b){IN_RELEASE}: Deallocated
+
+all use counts:
+c: 0 (-)
+1 foos
+
+Release something not gotten before: a get/put bug goes into negative count
+DFOO NOTICE foo(c){IN_USE}: c -1 kungfoo: now used by -1 (-1*kungfoo)
+DFOO ERROR foo(c){IN_USE}: Negative use count on kungfoo: -1 (-1*kungfoo)
+
+all use counts:
+c: -1 (-1*kungfoo)
+1 foos
+
+More negative
+DFOO NOTICE foo(c){IN_USE}: c -1 kungfoo: now used by -2 (-2*kungfoo)
+DFOO ERROR foo(c){IN_USE}: Negative use count on kungfoo: -2 (-2*kungfoo)
+DFOO NOTICE foo(c){IN_USE}: c -1 kungfoo: now used by -3 (-3*kungfoo)
+DFOO ERROR foo(c){IN_USE}: Negative use count on kungfoo: -3 (-3*kungfoo)
+
+all use counts:
+c: -3 (-3*kungfoo)
+1 foos
+
+Also release c
+DFOO NOTICE foo(c){IN_USE}: c +4 kungfoo: now used by 1 (kungfoo)
+DFOO NOTICE foo(c){IN_USE}: c -1 kungfoo: now used by 0 (-)
+DFOO DEBUG foo(c){IN_USE}: Received Event FOO_EV_UNUSED
+DFOO DEBUG foo(c){IN_USE}: state_chg to IN_RELEASE
+DFOO NOTICE foo(c){IN_RELEASE}: c +1 releasing: now used by 1 (releasing)
+Signal async release as done
+DFOO NOTICE foo(c){IN_RELEASE}: c -1 releasing: now used by 0 (-)
+DFOO DEBUG foo(c){IN_RELEASE}: Received Event FOO_EV_UNUSED
+DFOO DEBUG foo(c){IN_RELEASE}: Terminating (cause = OSMO_FSM_TERM_REGULAR)
+DFOO DEBUG foo(c){IN_RELEASE}: Freeing instance
+DFOO DEBUG foo(c){IN_RELEASE}: Deallocated
+
+all use counts:
+0 foos
+
diff --git a/tests/use_count/use_count_test.ok b/tests/use_count/use_count_test.ok
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/use_count/use_count_test.ok

-- 
To view, visit https://gerrit.osmocom.org/13121
To unsubscribe, or for help writing mail filters, visit https://gerrit.osmocom.org/settings

Gerrit-Project: libosmocore
Gerrit-Branch: master
Gerrit-MessageType: newchange
Gerrit-Change-Id: Ife31e6798b4e728a23913179e346552a7dd338c0
Gerrit-Change-Number: 13121
Gerrit-PatchSet: 1
Gerrit-Owner: Neels Hofmeyr <nhofmeyr at sysmocom.de>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.osmocom.org/pipermail/gerrit-log/attachments/20190304/13b7abbe/attachment-0001.html>


More information about the gerrit-log mailing list