Neels Hofmeyr | 0e8df1c | 2019-02-11 20:32:25 +0100 | [diff] [blame] | 1 | /*! \file use_count.h |
| 2 | * Generic object usage counter API (get, put and deallocate on zero count). |
| 3 | */ |
| 4 | /* |
| 5 | * (C) 2019 by sysmocom s.f.m.c. GmbH <info@sysmocom.de> |
| 6 | * |
| 7 | * All Rights Reserved |
| 8 | * |
| 9 | * Author: Neels Hofmeyr <neels@hofmeyr.de> |
| 10 | * |
| 11 | * SPDX-License-Identifier: GPL-2.0+ |
| 12 | * |
| 13 | * This program is free software; you can redistribute it and/or modify |
| 14 | * it under the terms of the GNU General Public License as published by |
| 15 | * the Free Software Foundation; either version 2 of the License, or |
| 16 | * (at your option) any later version. |
| 17 | * |
| 18 | * This program is distributed in the hope that it will be useful, |
| 19 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 20 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 21 | * GNU General Public License for more details. |
Neels Hofmeyr | 0e8df1c | 2019-02-11 20:32:25 +0100 | [diff] [blame] | 22 | */ |
| 23 | |
| 24 | #pragma once |
| 25 | |
| 26 | #include <stdint.h> |
| 27 | #include <stdlib.h> |
| 28 | |
| 29 | #include <osmocom/core/linuxlist.h> |
| 30 | |
| 31 | /*! \defgroup use_count Use Counter |
| 32 | * @{ |
| 33 | * \file use_count.h |
| 34 | */ |
| 35 | |
| 36 | struct osmo_use_count_entry; |
| 37 | |
| 38 | /*! Invoked when a use count changes. |
| 39 | * |
| 40 | * The implementation is free to trigger actions on arbitrary use count changes, typically to free the |
| 41 | * use_count->talloc_object when the total use count reaches zero. |
| 42 | * |
| 43 | * The implementation may modify use_count_entry->count, for example for handling of get()/put() bugs, to clamp specific use |
| 44 | * tokens to specific counts, or to prevent the caller from put()ting into negative counts. When returning an error, |
| 45 | * there is no implicit undo -- if errors need to be corrected, this function is responsible for that. |
| 46 | * |
| 47 | * Be aware: use token strings are not copied, and use count entries usually remain listed also when they reach a zero |
| 48 | * count. This is trivially perfectly ok when using string literals as use tokens. It is also possible to use |
| 49 | * dynamically allocated string tokens, but should a use token string become invalid memory when reaching zero count, it |
| 50 | * is the responsibility of this function to set the use_count_entry->use = NULL; this is required to avoid subsequent |
| 51 | * osmo_use_count_get_put() invocations from calling strcmp() on invalid memory. (Setting use = NULL cannot be done |
| 52 | * implicitly after this callback invocation, because callback implementations are allowed to completely deallocate the |
| 53 | * talloc_object and the use_count list entries, and setting use = NULL after that would be a use-after-free.) |
| 54 | * |
| 55 | * \param[in] use_count_entry Use count entry that is being modified. |
| 56 | * \param[in] old_use_count Use count the item had before the change in use count. |
| 57 | * \param[in] file Source file string, passed in as __FILE__ from macro osmo_use_count_get_put(). |
| 58 | * \param[in] line Source file line, passed in as __LINE__ from macro osmo_use_count_get_put(). |
| 59 | * \return 0 on success, negative if any undesired use count is reached; this rc will be returned by |
| 60 | * osmo_use_count_get_put(). |
| 61 | */ |
| 62 | typedef int (* osmo_use_count_cb_t )(struct osmo_use_count_entry *use_count_entry, int32_t old_use_count, |
| 63 | const char *file, int line); |
| 64 | |
| 65 | /*! Use counter state for one used object, managing N distinct named counters. |
| 66 | * |
| 67 | * Manage any number of uses of an object, with name tokens given to each use. |
| 68 | * |
| 69 | * A typical use tracking done by a single instance of this struct may look like: |
| 70 | * "VLR subscr MSISDN-23 + SMS-receiver: now used by 6 (attached,2*SMS-receiver,SMS-pending,SMS,Paging)" |
| 71 | * (This is a DREF log statement from an osmo-msc run delivering an SMS.) |
| 72 | * |
| 73 | * Use tokens are given as const char* strings. Typically string literals like "foo", __func__, or also NULL. Tokens may |
| 74 | * be dynamically allocated or static char[] buffers as long as they are guaranteed to remain unchanged while referenced |
| 75 | * by an osmo_use_count_entry. (Breakage occurs if one token magically changes to equal another listed token.) |
| 76 | * |
| 77 | * Instead of using string literals in the code directly, callers should use a #define, so that typos are caught at |
| 78 | * compile time rather than introducing obscure failures that are hard to spot for humans -- don't use foo_get("bar") |
| 79 | * and foo_put("bar"), but '#define FOO_USE_BAR "bar"' for foo_get(FOO_USE_BAR) and foo_put(FOO_USE_BAR). |
| 80 | * |
| 81 | * Counts are int32_t values, a separate count per use token string. Counts can be negative, though in the typical use |
| 82 | * case are only positive or 0. Enforcing a range is entirely up to the osmo_use_count_cb_t() implementation. |
| 83 | * |
| 84 | * The talloc_object must be a pointer eligible to be a talloc context, i.e. either obtained from a function like |
| 85 | * talloc_zero() or NULL. talloc_object is typically a pointer to the object that this struct is a member of. Use count |
| 86 | * entries may be allocated as talloc children of this (see also "Avoiding dynamic allocation" below). |
| 87 | * |
| 88 | * The use_cb() implementation allows to trigger actions when reaching specific use counts, e.g. deallocate when |
| 89 | * reaching a total sum across all use tokens of zero. |
| 90 | * |
| 91 | * On initialization, this struct can be left fully zero initialized (the llist_head use_counts is implicitly |
| 92 | * initialized upon the first osmo_use_count_get_put()). Usually, set only a talloc_object and a use_cb, though neither |
| 93 | * is strictly required. |
| 94 | * |
| 95 | * Avoiding dynamic allocation: dynamic allocation can be avoided completely by providing sufficient static use count |
| 96 | * entries with osmo_use_count_make_static_entries(). Otherwise, each new use token will dynamically allocate a new |
| 97 | * osmo_use_count_entry; note that once allocated, these entries stay around even if they reached an entry count of |
| 98 | * zero, and will be re-used for subsequent use count tokens. So even if not using osmo_use_count_make_static_entries(), |
| 99 | * each osmo_use_count will keep dynamic allocations at a minimum. See also the documentation for osmo_use_count_cb_t. |
| 100 | * |
| 101 | * List traversal considerations: your typical use count list would max at about six entries in practice. Traversing six |
| 102 | * llist->next pointers is less effort than doing a common strlen(). |
| 103 | * |
| 104 | * Obtaining the total use count: osmo_use_count_total() traverses all use token entries and forms a sum. It is trivial |
| 105 | * to keep a separate total count that completely avoids the need for calling this function, which is entirely up to the |
| 106 | * individual osmo_use_count_cb_t() implementation. The optimization gained is usually not worth it, though. |
| 107 | * |
| 108 | * Use token comparison considerations: strcmp() to compare use tokens is a fairly good tradeoff: |
| 109 | * - when the strings differ, strcmp() usually exits on the first or second character. |
| 110 | * - when the strings are identical, they are usually the exact same char* address (from compile-time string constant), |
| 111 | * meaning that strcmp() is completely skipped. |
| 112 | * (quote: "if (e->use == use || (use && e->use && !strcmp(e->use, use)))") |
| 113 | * - if we specified compile-time string constant use as requirement, we wouldn't need strcmp() at all, but this |
| 114 | * minuscule overhead has the benefit of complete correctness for any kinds of use token strings. |
| 115 | * |
| 116 | * Example: |
| 117 | * |
| 118 | * struct foo { |
| 119 | * struct osmo_use_count use_count; |
| 120 | * }; |
| 121 | * |
| 122 | * // Convenience macros for struct foo instances. These are strict about use count errors. |
| 123 | * #define foo_get(FOO, USE) OSMO_ASSERT( osmo_use_count_get_put(&(FOO)->use_count, USE, 1) == 0 ); |
| 124 | * #define foo_put(FOO, USE) OSMO_ASSERT( osmo_use_count_get_put(&(FOO)->use_count, USE, -1) == 0 ); |
| 125 | * |
| 126 | * int foo_use_cb(struct osmo_use_count_entry *use_count_entry, int32_t old_use_count, const char *file, int line) |
| 127 | * { |
| 128 | * struct foo *foo = use_count_entry->use_count->talloc_object; |
Pau Espin Pedrol | 0e7349d | 2020-07-15 15:41:51 +0200 | [diff] [blame] | 129 | * if (osmo_use_count_total(use_count_entry->use_count) == 0) |
Neels Hofmeyr | 0e8df1c | 2019-02-11 20:32:25 +0100 | [diff] [blame] | 130 | * talloc_free(foo); |
Pau Espin Pedrol | 0e7349d | 2020-07-15 15:41:51 +0200 | [diff] [blame] | 131 | * return 0; |
Neels Hofmeyr | 0e8df1c | 2019-02-11 20:32:25 +0100 | [diff] [blame] | 132 | * } |
| 133 | * |
| 134 | * // The function name is a convenient use token: |
| 135 | * void rx_stop_baz_request(struct foo *foo) |
| 136 | * { |
| 137 | * foo_get(foo, __func__); |
| 138 | * |
| 139 | * foo_put(foo, "baz"); |
| 140 | * printf("Stopped Bazing (%p)\n", foo); |
| 141 | * |
| 142 | * foo_put(foo, __func__); |
| 143 | * } |
| 144 | * |
| 145 | * void use_count_example() |
| 146 | * { |
| 147 | * struct foo *foo = talloc_zero(ctx, struct foo); |
| 148 | * *foo = (struct foo){ |
| 149 | * .use_count = { |
| 150 | * .talloc_object = foo, |
| 151 | * .use_cb = foo_use_cb, |
| 152 | * }, |
| 153 | * }; |
| 154 | * |
| 155 | * foo_get(foo, "bar"); // one osmo_use_count_entry was allocated |
| 156 | * foo_get(foo, "baz"); // a second osmo_use_count_entry was allocated |
| 157 | * foo_get(foo, "baz"); // still two entries |
| 158 | * |
| 159 | * printf("use: %s\n", osmo_use_count_name_buf(namebuf, sizeof(namebuf), &foo->use_count)); |
| 160 | * // "use: 3 (bar,2*baz)" |
| 161 | * |
| 162 | * foo_put(foo, "bar"); // still two entries, one entry is idle ("bar"=0) |
| 163 | * foo_put(foo, "baz"); |
| 164 | * rx_stop_baz_request(foo); |
| 165 | * // Final "baz" was put(), foo_use_cb() deallocated object foo, as well as all use count entries. |
| 166 | * }; |
| 167 | */ |
| 168 | struct osmo_use_count { |
| 169 | /*! Context to talloc-allocate use count entries from (if at all necessary); back-pointer to the owning object |
| 170 | * for osmo_use_count_cb_t implementations. */ |
| 171 | void *talloc_object; |
| 172 | /*! If not NULL, this is invoked for each use count change. */ |
| 173 | osmo_use_count_cb_t use_cb; |
| 174 | /*! List of use tokens. No need to touch this, the llist is initialized implicitly. */ |
| 175 | struct llist_head use_counts; |
| 176 | }; |
| 177 | |
| 178 | /*! One named counter in the list managed by osmo_use_count. |
| 179 | * Gets created as necessary by osmo_use_count_get_put(). The total current use count of an object is the sum of all |
| 180 | * individual osmo_use_count_entry->count. |
| 181 | * |
| 182 | * object <--backpointer-+ |
| 183 | * t| .osmo_use_count | |
| 184 | * a| .talloc_object ------------+ |
| 185 | * l| .use_counts llist: use count |
| 186 | * l|-> - osmo_use_count_entry: "foo" 1 |
| 187 | * o|-> - osmo_use_count_entry: "bar" 3 |
| 188 | * c|-> - osmo_use_count_entry: "baz" 0 (currently unused entry) |
| 189 | */ |
| 190 | struct osmo_use_count_entry { |
| 191 | /*! Entry in osmo_use_count->use_counts. */ |
| 192 | struct llist_head entry; |
| 193 | /*! Parent use count and backpointer to the talloc_object. */ |
| 194 | struct osmo_use_count *use_count; |
| 195 | /*! Use token string that was passed to osmo_use_count_get_put(). */ |
| 196 | const char *use; |
| 197 | /*! Current use count amount for only this use token string. |
| 198 | * If zero, this entry is currently unused and kept around to avoid frequent de-/allocation. */ |
| 199 | int32_t count; |
| 200 | }; |
| 201 | |
| 202 | /*! Change the use count for a given use token. |
| 203 | * \param USE_LIST A struct osmo_use_count*, e.g. &my_obj->use_count. |
| 204 | * \param USE A use token: arbitrary string (const char*). This must remain valid memory, e.g. string constants. |
| 205 | * \param CHANGE Signed integer value to add to the use count: positive means get(), negative means put(). |
| 206 | * \return Negative on range violations or USE_LIST == NULL, the use_cb()'s return value, or 0 on success. |
| 207 | */ |
| 208 | #define osmo_use_count_get_put(USE_LIST, USE, CHANGE) \ |
| 209 | _osmo_use_count_get_put(USE_LIST, USE, CHANGE, __FILE__, __LINE__) |
| 210 | |
| 211 | int _osmo_use_count_get_put(struct osmo_use_count *uc, const char *use, int32_t change, |
| 212 | const char *file, int line); |
| 213 | |
| 214 | const char *osmo_use_count_name_buf(char *buf, size_t buf_len, const struct osmo_use_count *uc); |
Neels Hofmeyr | 9277b97 | 2020-09-15 00:48:36 +0000 | [diff] [blame] | 215 | int osmo_use_count_to_str_buf(char *buf, size_t buf_len, const struct osmo_use_count *uc); |
| 216 | char *osmo_use_count_to_str_c(void *ctx, const struct osmo_use_count *uc); |
Neels Hofmeyr | 0e8df1c | 2019-02-11 20:32:25 +0100 | [diff] [blame] | 217 | |
| 218 | int32_t osmo_use_count_total(const struct osmo_use_count *uc); |
| 219 | int32_t osmo_use_count_by(const struct osmo_use_count *uc, const char *use); |
| 220 | |
| 221 | struct osmo_use_count_entry *osmo_use_count_find(const struct osmo_use_count *uc, const char *use); |
| 222 | void osmo_use_count_free(struct osmo_use_count_entry *use_count_entry); |
| 223 | |
| 224 | void osmo_use_count_make_static_entries(struct osmo_use_count *uc, struct osmo_use_count_entry *buf, |
| 225 | size_t buf_n_entries); |
| 226 | |
| 227 | /*! @} */ |