| /*! \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); |
| } |
| } |
| |
| /*! @} */ |