blob: 453d2ae8c261b148045e64b10477d985381b4653 [file] [log] [blame]
Neels Hofmeyr0e8df1c2019-02-11 20:32:25 +01001/*! \file use_count.c
2 * Generic object usage counter Implementation (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.
22 *
23 * You should have received a copy of the GNU General Public License along
24 * with this program; if not, write to the Free Software Foundation, Inc.,
25 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 */
27
28#include <errno.h>
29#include <inttypes.h>
30#include <string.h>
31
32#include <osmocom/core/linuxlist.h>
33#include <osmocom/core/utils.h>
34#include <osmocom/core/use_count.h>
35
36/*! \addtogroup use_count
37 *
38 * Generic object usage counter (get, put and deallocate on zero count).
39 *
40 * For an example and a detailed description, see struct osmo_use_count.
41 *
42 * @{
43 * \file use_count.c
44 */
45
46/*! Add two int32_t but make sure to min- and max-clamp at INT32_MIN and INT32_MAX, respectively. */
47static inline bool count_safe(int32_t *val_p, int32_t add)
48{
49 int32_t val = *val_p;
50
51 /* A simpler implementation would just let the integer overflow and compare with previous value afterwards, but
52 * that causes runtime errors in the address sanitizer. So let's just do this without tricks. */
53 if (add < 0 && val < 0 && val - INT32_MIN < -add) {
54 *val_p = INT32_MIN;
55 return false;
56 }
57
58 if (add > 0 && val > 0 && INT32_MAX - val < add) {
59 *val_p = INT32_MAX;
60 return false;
61 }
62
63 *val_p = val + add;
64 return true;
65}
66
67/*! Return the sum of all use counts, min- and max-clamped at INT32_MIN and INT32_MAX.
68 * \param[in] uc Use counts to sum up.
69 * \return Accumulated counts, or 0 if uc is NULL.
70 */
71int32_t osmo_use_count_total(const struct osmo_use_count *uc)
72{
73 struct osmo_use_count_entry *e;
74 int32_t total = 0;
75
76 if (!uc || !uc->use_counts.next)
77 return 0;
78
79 llist_for_each_entry(e, &uc->use_counts, entry) {
80 count_safe(&total, e->count);
81 }
82 return total;
83}
84
85/*! Return use count by a single use token.
86 * \param[in] uc Use counts to look up in.
87 * \param[in] use Use token.
88 * \return Use count, or 0 if uc is NULL or use token is not present.
89 */
90int32_t osmo_use_count_by(const struct osmo_use_count *uc, const char *use)
91{
92 const struct osmo_use_count_entry *e;
93 if (!uc)
94 return 0;
95 e = osmo_use_count_find(uc, use);
96 if (!e)
97 return 0;
98 return e->count;
99}
100
101/*! Write a comprehensive listing of use counts to a string buffer.
102 * Reads like "12 (3*barring,fighting,8*kungfoo)".
103 * \param[inout] buf Destination buffer.
104 * \param[in] buf_len sizeof(buf).
105 * \param[in] uc Use counts to print.
106 * \return buf, always nul-terminated (except when buf_len < 1).
107 */
108const char *osmo_use_count_name_buf(char *buf, size_t buf_len, const struct osmo_use_count *uc)
109{
110 int32_t count = osmo_use_count_total(uc);
111 struct osmo_strbuf sb = { .buf = buf, .len = buf_len };
112 struct osmo_use_count_entry *e;
113 bool first;
114
115 OSMO_STRBUF_PRINTF(sb, "%" PRId32 " (", count);
116
117 first = true;
118 llist_for_each_entry(e, &uc->use_counts, entry) {
119 if (!e->count)
120 continue;
121 if (!first)
122 OSMO_STRBUF_PRINTF(sb, ",");
123 first = false;
124 if (e->count != 1)
125 OSMO_STRBUF_PRINTF(sb, "%" PRId32 "*", e->count);
126 OSMO_STRBUF_PRINTF(sb, "%s", e->use ? : "NULL");
127 }
128 if (first)
129 OSMO_STRBUF_PRINTF(sb, "-");
130 OSMO_STRBUF_PRINTF(sb, ")");
131 return buf;
132}
133
134/* Return a use token's use count entry -- probably you want osmo_use_count_by() instead.
135 * \param[in] uc Use counts to look up in.
136 * \param[in] use Use token.
137 * \return matching entry, or NULL if not present.
138 */
139struct osmo_use_count_entry *osmo_use_count_find(const struct osmo_use_count *uc, const char *use)
140{
141 struct osmo_use_count_entry *e;
142 if (!uc->use_counts.next)
143 return NULL;
144 llist_for_each_entry(e, &uc->use_counts, entry) {
145 if (e->use == use || (use && e->use && !strcmp(e->use, use)))
146 return e;
147 }
148 return NULL;
149}
150
151/*! Find a use count entry that currently has zero count, and re-use that for this new use token. */
152static struct osmo_use_count_entry *osmo_use_count_repurpose_zero_entry(struct osmo_use_count *uc, const char *use)
153{
154 struct osmo_use_count_entry *e;
155 if (!uc->use_counts.next)
156 return NULL;
157 llist_for_each_entry(e, &uc->use_counts, entry) {
158 if (!e->count) {
159 e->use = use;
160 return e;
161 }
162 }
163 return NULL;
164}
165
166/*! Allocate a new use count entry, happens implicitly in osmo_use_count_get_put(). */
167static struct osmo_use_count_entry *osmo_use_count_create(struct osmo_use_count *uc, const char *use)
168{
169 struct osmo_use_count_entry *e = talloc_zero(uc->talloc_object, struct osmo_use_count_entry);
170 if (!e)
171 return NULL;
172 *e = (struct osmo_use_count_entry){
173 .use_count = uc,
174 .use = use,
175 };
176 if (!uc->use_counts.next)
177 INIT_LLIST_HEAD(&uc->use_counts);
178 llist_add_tail(&e->entry, &uc->use_counts);
179 return e;
180}
181
182/*! Deallocate a use count entry.
183 * Normally, this is not necessary -- it is ok and even desirable to leave use count entries around even when they reach
184 * a count of zero, until the use_count->talloc_object deallocates and removes all of them in one flush. This avoids
185 * repeated allocation and deallocation for use tokens, because use count entries that have reached zero count are
186 * repurposed for any other use tokens. A cleanup makes sense only if a very large number of differing use tokens surged
187 * at the same time, and the owning object will not be deallocated soon; if so, this should be done by the
188 * osmo_use_count_cb_t implementation.
189 *
190 * osmo_use_count_free() must *not* be called on use count entries that were added by
191 * osmo_use_count_make_static_entries(). This is the responsibility of the osmo_use_count_cb_t() implementation.
192 *
193 * \param[in] use_count_entry Use count entry to unlist and free.
194 */
195void osmo_use_count_free(struct osmo_use_count_entry *use_count_entry)
196{
197 if (!use_count_entry)
198 return;
199 llist_del(&use_count_entry->entry);
200 talloc_free(use_count_entry);
201}
202
203/*! Implementation for osmo_use_count_get_put(), which can also be directly invoked to pass source file information. For
204 * arguments besides file and line, see osmo_use_count_get_put().
205 * \param[in] file Source file path, as in __FILE__.
206 * \param[in] line Source file line, as in __LINE__.
207 */
208int _osmo_use_count_get_put(struct osmo_use_count *uc, const char *use, int32_t change,
209 const char *file, int line)
210{
211 struct osmo_use_count_entry *e;
212 int32_t old_use_count;
213 if (!change)
214 return 0;
215
216 e = osmo_use_count_find(uc, use);
217 if (!e)
218 e = osmo_use_count_repurpose_zero_entry(uc, use);
219 if (!e)
220 e = osmo_use_count_create(uc, use);
221 if (!e)
222 return -ENOMEM;
223
224 if (!e->count) {
225 /* move to end */
226 llist_del(&e->entry);
227 llist_add_tail(&e->entry, &uc->use_counts);
228 }
229
230 old_use_count = e->count;
231 if (!count_safe(&e->count, change)) {
232 e->count = old_use_count;
233 return -ERANGE;
234 }
235
236 if (uc->use_cb)
237 return uc->use_cb(e, old_use_count, file, line);
238 return 0;
239}
240
241/*! Add N static use token entries to avoid dynamic allocation of use count tokens.
242 * When not using this function, use count entries are talloc allocated from uc->talloc_object as talloc context. This
243 * means that there are small dynamic allocations for each use count token. osmo_use_count_get_put() normally leaves
244 * zero-count entries around and re-purposes them later, so the number of small allocations is at most the number of
245 * concurrent differently-named uses of the same object. If that is not enough, this function allows completely avoiding
246 * dynamic use count allocations, by adding N static entries with a zero count and a NULL use token. They will be used
247 * by osmo_use_count_get_put(), and, if the caller avoids using osmo_use_count_free(), the osmo_use_count implementation
248 * never deallocates them. The idea is that the entries are members of the uc->talloc_object, or that they will by other
249 * means be implicitly deallocated by the talloc_object. It is fine to call
250 * osmo_use_count_make_static_entries(buf_n_entries=N) and later have more than N concurrent uses, i.e. it is no problem
251 * to mix static and dynamic entries. To completely avoid dynamic use count entries, N has to >= the maximum number of
252 * concurrent differently-named uses that will occur in the lifetime of the talloc_object.
253 *
254 * struct my_object {
255 * struct osmo_use_count use_count;
256 * struct osmo_use_count_entry use_count_buf[3]; // planning for 3 concurrent users
257 * };
258 *
259 * void example() {
260 * struct my_object *o = talloc_zero(ctx, struct my_object);
261 * osmo_use_count_make_static_entries(&o->use_count, o->use_count_buf, ARRAY_SIZE(o->use_count_buf));
262 * }
263 */
264void osmo_use_count_make_static_entries(struct osmo_use_count *uc, struct osmo_use_count_entry *buf,
265 size_t buf_n_entries)
266{
267 size_t idx;
268 if (!uc->use_counts.next)
269 INIT_LLIST_HEAD(&uc->use_counts);
270 for (idx = 0; idx < buf_n_entries; idx++) {
271 struct osmo_use_count_entry *e = &buf[idx];
272 *e = (struct osmo_use_count_entry){
273 .use_count = uc,
274 };
275 llist_add_tail(&e->entry, &uc->use_counts);
276 }
277}
278
279/*! @} */