mirror of https://gerrit.osmocom.org/libosmocore
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 (more than one use count per user category), - directly use arbitrary names for uses like __func__ or "foo" (no need to define enums and value_string[]s), - re-use the same code for e.g. vlr_subscr and get fairly detailed VLR susbscriber usage logging for free. Change-Id: Ife31e6798b4e728a23913179e346552a7dd338c0
This commit is contained in:
parent
0c7826e9bd
commit
0e8df1c7e4
|
@ -53,6 +53,7 @@ nobase_include_HEADERS = \
|
||||||
osmocom/core/utils.h \
|
osmocom/core/utils.h \
|
||||||
osmocom/core/write_queue.h \
|
osmocom/core/write_queue.h \
|
||||||
osmocom/core/sockaddr_str.h \
|
osmocom/core/sockaddr_str.h \
|
||||||
|
osmocom/core/use_count.h \
|
||||||
osmocom/crypt/auth.h \
|
osmocom/crypt/auth.h \
|
||||||
osmocom/crypt/gprs_cipher.h \
|
osmocom/crypt/gprs_cipher.h \
|
||||||
osmocom/ctrl/control_cmd.h \
|
osmocom/ctrl/control_cmd.h \
|
||||||
|
|
|
@ -0,0 +1,228 @@
|
||||||
|
/*! \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@sysmocom.de>
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*
|
||||||
|
* Author: Neels Hofmeyr <neels@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, and setting use = NULL after that would be a use-after-free.)
|
||||||
|
*
|
||||||
|
* \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, managing N distinct named counters.
|
||||||
|
*
|
||||||
|
* Manage any number of uses of an object, with name tokens given to each use.
|
||||||
|
*
|
||||||
|
* A typical use tracking done by a single instance of this struct may look like:
|
||||||
|
* "VLR subscr MSISDN-23 + SMS-receiver: now used by 6 (attached,2*SMS-receiver,SMS-pending,SMS,Paging)"
|
||||||
|
* (This is a DREF log statement from an osmo-msc run delivering an SMS.)
|
||||||
|
*
|
||||||
|
* Use tokens are given as const char* strings. Typically string literals like "foo", __func__, or also NULL. Tokens may
|
||||||
|
* be dynamically allocated or static char[] buffers as long as they are guaranteed to remain unchanged while referenced
|
||||||
|
* by an osmo_use_count_entry. (Breakage occurs if one token magically changes to equal another listed token.)
|
||||||
|
*
|
||||||
|
* Instead of using string literals in the code directly, callers should use a #define, so that typos are caught at
|
||||||
|
* compile time rather than introducing obscure failures that are hard to spot for humans -- don't use foo_get("bar")
|
||||||
|
* and foo_put("bar"), but '#define FOO_USE_BAR "bar"' for foo_get(FOO_USE_BAR) and foo_put(FOO_USE_BAR).
|
||||||
|
*
|
||||||
|
* Counts are int32_t values, a separate count per use token string. Counts can be negative, though in the typical use
|
||||||
|
* case are only positive or 0. Enforcing a range is entirely up to the osmo_use_count_cb_t() implementation.
|
||||||
|
*
|
||||||
|
* The talloc_object must be a pointer eligible to be a talloc context, i.e. either obtained from a function like
|
||||||
|
* talloc_zero() or NULL. talloc_object is typically a pointer to the object that this struct is a member of. Use count
|
||||||
|
* entries may be allocated as talloc children of this (see also "Avoiding dynamic allocation" below).
|
||||||
|
*
|
||||||
|
* The use_cb() implementation allows to trigger actions when reaching specific use counts, e.g. deallocate when
|
||||||
|
* reaching a total sum across all use tokens of zero.
|
||||||
|
*
|
||||||
|
* On initialization, this struct can be left fully zero initialized (the llist_head use_counts is implicitly
|
||||||
|
* initialized upon the first osmo_use_count_get_put()). Usually, set only a talloc_object and a use_cb, though neither
|
||||||
|
* is strictly required.
|
||||||
|
*
|
||||||
|
* Avoiding dynamic allocation: dynamic allocation can be avoided completely by providing sufficient static use count
|
||||||
|
* entries with osmo_use_count_make_static_entries(). Otherwise, each new use token will dynamically allocate a new
|
||||||
|
* osmo_use_count_entry; note that once allocated, these entries stay around even if they reached an entry count of
|
||||||
|
* zero, and will be re-used for subsequent use count tokens. So even if not using osmo_use_count_make_static_entries(),
|
||||||
|
* each osmo_use_count will keep dynamic allocations at a minimum. See also the documentation for osmo_use_count_cb_t.
|
||||||
|
*
|
||||||
|
* List traversal considerations: your typical use count list would max at about six entries in practice. Traversing six
|
||||||
|
* llist->next pointers is less effort than doing a common strlen().
|
||||||
|
*
|
||||||
|
* Obtaining the total use count: osmo_use_count_total() traverses all use token entries and forms a sum. It is trivial
|
||||||
|
* to keep a separate total count that completely avoids the need for calling this function, which is entirely up to the
|
||||||
|
* individual osmo_use_count_cb_t() implementation. The optimization gained is usually not worth it, though.
|
||||||
|
*
|
||||||
|
* Use token comparison considerations: strcmp() to compare use tokens is a fairly good tradeoff:
|
||||||
|
* - when the strings differ, strcmp() usually exits on the first or second character.
|
||||||
|
* - when the strings are identical, they are usually the exact same char* address (from compile-time string constant),
|
||||||
|
* meaning that strcmp() is completely skipped.
|
||||||
|
* (quote: "if (e->use == use || (use && e->use && !strcmp(e->use, use)))")
|
||||||
|
* - if we specified compile-time string constant use as requirement, we wouldn't need strcmp() at all, but this
|
||||||
|
* minuscule overhead has the benefit of complete correctness for any kinds of use token strings.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
*
|
||||||
|
* 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"); // one osmo_use_count_entry was allocated
|
||||||
|
* foo_get(foo, "baz"); // a second osmo_use_count_entry was allocated
|
||||||
|
* foo_get(foo, "baz"); // still two entries
|
||||||
|
*
|
||||||
|
* printf("use: %s\n", osmo_use_count_name_buf(namebuf, sizeof(namebuf), &foo->use_count));
|
||||||
|
* // "use: 3 (bar,2*baz)"
|
||||||
|
*
|
||||||
|
* foo_put(foo, "bar"); // still two entries, one entry is idle ("bar"=0)
|
||||||
|
* foo_put(foo, "baz");
|
||||||
|
* rx_stop_baz_request(foo);
|
||||||
|
* // Final "baz" was put(), foo_use_cb() deallocated object foo, as well as all use count entries.
|
||||||
|
* };
|
||||||
|
*/
|
||||||
|
struct osmo_use_count {
|
||||||
|
/*! Context to talloc-allocate use count entries from (if at all necessary); 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*! One named counter in the list managed by osmo_use_count.
|
||||||
|
* Gets created as necessary by osmo_use_count_get_put(). The total current use count of an object is the sum of all
|
||||||
|
* individual osmo_use_count_entry->count.
|
||||||
|
*
|
||||||
|
* object <--backpointer-+
|
||||||
|
* t| .osmo_use_count |
|
||||||
|
* a| .talloc_object ------------+
|
||||||
|
* l| .use_counts llist: use count
|
||||||
|
* l|-> - osmo_use_count_entry: "foo" 1
|
||||||
|
* o|-> - osmo_use_count_entry: "bar" 3
|
||||||
|
* c|-> - osmo_use_count_entry: "baz" 0 (currently unused entry)
|
||||||
|
*/
|
||||||
|
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 amount for only this use token string.
|
||||||
|
* If zero, this entry is currently unused and kept around to avoid frequent de-/allocation. */
|
||||||
|
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 change,
|
||||||
|
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);
|
||||||
|
|
||||||
|
/*! @} */
|
|
@ -26,6 +26,7 @@ libosmocore_la_SOURCES = timer.c timer_gettimeofday.c timer_clockgettime.c \
|
||||||
isdnhdlc.c \
|
isdnhdlc.c \
|
||||||
tdef.c \
|
tdef.c \
|
||||||
sockaddr_str.c \
|
sockaddr_str.c \
|
||||||
|
use_count.c \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
if HAVE_SSSE3
|
if HAVE_SSSE3
|
||||||
|
|
|
@ -0,0 +1,279 @@
|
||||||
|
/*! \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@sysmocom.de>
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*
|
||||||
|
* Author: Neels Hofmeyr <neels@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 and a detailed description, 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 -- probably you want osmo_use_count_by() instead.
|
||||||
|
* \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 very large number of differing use tokens surged
|
||||||
|
* at the same time, and the owning object will not be deallocated soon; 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(). This is the responsibility of the osmo_use_count_cb_t() implementation.
|
||||||
|
*
|
||||||
|
* \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 talloc context. This
|
||||||
|
* means that there are small dynamic allocations for each use count token. 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 differently-named uses of the same object. 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 differently-named uses 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*! @} */
|
|
@ -31,6 +31,7 @@ check_PROGRAMS = timer/timer_test sms/sms_test ussd/ussd_test \
|
||||||
tdef/tdef_vty_test_config_subnode \
|
tdef/tdef_vty_test_config_subnode \
|
||||||
tdef/tdef_vty_test_dynamic \
|
tdef/tdef_vty_test_dynamic \
|
||||||
sockaddr_str/sockaddr_str_test \
|
sockaddr_str/sockaddr_str_test \
|
||||||
|
use_count/use_count_test \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
if ENABLE_MSGFILE
|
if ENABLE_MSGFILE
|
||||||
|
@ -240,6 +241,9 @@ tdef_tdef_vty_test_dynamic_LDADD = $(LDADD) $(top_builddir)/src/vty/libosmovty.l
|
||||||
sockaddr_str_sockaddr_str_test_SOURCES = sockaddr_str/sockaddr_str_test.c
|
sockaddr_str_sockaddr_str_test_SOURCES = sockaddr_str/sockaddr_str_test.c
|
||||||
sockaddr_str_sockaddr_str_test_LDADD = $(LDADD)
|
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)
|
||||||
|
|
||||||
# The `:;' works around a Bash 3.2 bug when the output is not writeable.
|
# The `:;' works around a Bash 3.2 bug when the output is not writeable.
|
||||||
$(srcdir)/package.m4: $(top_srcdir)/configure.ac
|
$(srcdir)/package.m4: $(top_srcdir)/configure.ac
|
||||||
:;{ \
|
:;{ \
|
||||||
|
@ -309,6 +313,7 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \
|
||||||
tdef/tdef_vty_test_config_subnode.vty \
|
tdef/tdef_vty_test_config_subnode.vty \
|
||||||
tdef/tdef_vty_test_dynamic.vty \
|
tdef/tdef_vty_test_dynamic.vty \
|
||||||
sockaddr_str/sockaddr_str_test.ok \
|
sockaddr_str/sockaddr_str_test.ok \
|
||||||
|
use_count/use_count_test.ok use_count/use_count_test.err \
|
||||||
$(NULL)
|
$(NULL)
|
||||||
|
|
||||||
DISTCLEANFILES = atconfig atlocal conv/gsm0503_test_vectors.c
|
DISTCLEANFILES = atconfig atlocal conv/gsm0503_test_vectors.c
|
||||||
|
|
|
@ -337,3 +337,10 @@ AT_KEYWORDS([sockaddr_str])
|
||||||
cat $abs_srcdir/sockaddr_str/sockaddr_str_test.ok > expout
|
cat $abs_srcdir/sockaddr_str/sockaddr_str_test.ok > expout
|
||||||
AT_CHECK([$abs_top_builddir/tests/sockaddr_str/sockaddr_str_test], [0], [expout], [ignore])
|
AT_CHECK([$abs_top_builddir/tests/sockaddr_str/sockaddr_str_test], [0], [expout], [ignore])
|
||||||
AT_CLEANUP
|
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
|
||||||
|
|
|
@ -0,0 +1,339 @@
|
||||||
|
/* Test implementation for osmo_use_count API. */
|
||||||
|
/*
|
||||||
|
* (C) 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
|
||||||
|
*
|
||||||
|
* All Rights Reserved
|
||||||
|
*
|
||||||
|
* Author: Neels Hofmeyr <neels@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;
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue