diff options
author | Neels Hofmeyr <neels@hofmeyr.de> | 2019-02-11 20:32:25 +0100 |
---|---|---|
committer | Neels Hofmeyr <nhofmeyr@sysmocom.de> | 2019-04-08 13:47:17 +0000 |
commit | 0e8df1c7e48bcae2285c7c138bd50f932049bd24 (patch) | |
tree | ebc61be55958db4925f412bfb55906cf100d94eb /src | |
parent | 0c7826e9bd8026b239d320dba49416fd603f17fd (diff) |
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
Diffstat (limited to 'src')
-rw-r--r-- | src/Makefile.am | 1 | ||||
-rw-r--r-- | src/use_count.c | 279 |
2 files changed, 280 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 1fae8b0a..aaf2233e 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -26,6 +26,7 @@ libosmocore_la_SOURCES = timer.c timer_gettimeofday.c timer_clockgettime.c \ isdnhdlc.c \ tdef.c \ sockaddr_str.c \ + use_count.c \ $(NULL) if HAVE_SSSE3 diff --git a/src/use_count.c b/src/use_count.c new file mode 100644 index 00000000..453d2ae8 --- /dev/null +++ b/src/use_count.c @@ -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); + } +} + +/*! @} */ |