The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
Loading...
Searching...
No Matches
rlm_cache_rbtree.c
Go to the documentation of this file.
1/*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: addd51ae94b6a90f0a55132ba2910c779c4ed98c $
19 * @file rlm_cache_rbtree.c
20 * @brief Simple rbtree based cache.
21 *
22 * @copyright 2014 The FreeRADIUS server project
23 */
24#include <freeradius-devel/server/base.h>
25#include <freeradius-devel/util/debug.h>
26#include "../../rlm_cache.h"
27
28typedef struct {
29 fr_rb_tree_t *cache; //!< Tree for looking up cache keys.
30 fr_heap_t *heap; //!< For managing entry expiry.
31
32 pthread_mutex_t mutex; //!< Protect the tree from multiple readers/writers.
34
35typedef struct {
36 rlm_cache_rbtree_mutable_t *mutable; //!< Mutable instance data.
38
39typedef struct {
40 rlm_cache_entry_t fields; //!< Entry data.
41
42 fr_rb_node_t node; //!< Entry used for lookups.
43 fr_heap_index_t heap_id; //!< Offset used for expiry heap.
45
46/** Compare two entries by key
47 *
48 * There may only be one entry with the same key.
49 */
50static fr_cmp_ret_t cache_entry_cmp(void const *one, void const *two)
51{
52 rlm_cache_entry_t const *a = one, *b = two;
53
54 return MEMCMP_FIELDS(a, b, key.vb_strvalue, key.vb_length);
55}
56
57/** Compare two entries by expiry time
58 *
59 * There may be multiple entries with the same expiry time.
60 */
61static fr_cmp_ret_t cache_heap_cmp(void const *one, void const *two)
62{
63 rlm_cache_entry_t const *a = one, *b = two;
64
65 return fr_unix_time_cmp(a->expires, b->expires);
66}
67
68/** Custom allocation function for the driver
69 *
70 * Allows allocation of cache entry structures with additional fields.
71 *
72 * @copydetails cache_entry_alloc_t
73 */
75 request_t *request)
76{
78
79 c = talloc_zero(NULL, rlm_cache_rb_entry_t);
80 if (!c) {
81 RERROR("Failed allocating cache entry");
82 return NULL;
83 }
84
85 return (rlm_cache_entry_t *)c;
86}
87
88/** Locate a cache entry
89 *
90 * @note handle not used except for sanity checks.
91 *
92 * @copydetails cache_entry_find_t
93 */
95 UNUSED rlm_cache_config_t const *config, void *instance,
96 request_t *request, UNUSED void *handle, fr_value_box_t const *key)
97{
98 rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
99 rlm_cache_rbtree_mutable_t *mutable = driver->mutable;
100 rlm_cache_entry_t find = {};
101 int i;
103
104 fr_assert(mutable->cache);
105
106 /*
107 * Clear out old entries
108 */
109 for (i = 0; i < 3; i++) {
110 c = fr_heap_peek(mutable->heap);
111 if (!c) break;
112
113 if (fr_unix_time_gt(c->expires, fr_time_to_unix_time(request->packet->timestamp))) break;
114
115 fr_heap_extract(&mutable->heap, c);
116 fr_rb_delete(mutable->cache, c);
117 talloc_free(c);
118 }
119
120 fr_value_box_copy_shallow(NULL, &find.key, key);
121
122 /*
123 * Is there an entry for this key?
124 *
125 * A comparator error must not read as a miss: a miss
126 * triggers "insert as new", which would shadow the
127 * existing entry.
128 */
129 if (unlikely(fr_rb_find((void **)&c, mutable->cache, &find) < 0)) {
130 RPEDEBUG("Cache lookup failed");
131 *out = NULL;
132 return CACHE_ERROR;
133 }
134 if (!c) {
135 *out = NULL;
136 return CACHE_MISS;
137 }
138 *out = c;
139
140 return CACHE_OK;
141}
142
143/** Free an entry and remove it from the data store
144 *
145 * @note handle not used except for sanity checks.
146 *
147 * @copydetails cache_entry_expire_t
148 */
150 request_t *request, UNUSED void *handle,
151 fr_value_box_t const *key)
152{
153 rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
154 rlm_cache_entry_t find = {};
156
157 if (!request) return CACHE_ERROR;
158
159 fr_value_box_copy_shallow(NULL, &find.key, key);
160
161 if (unlikely(fr_rb_find((void **)&c, driver->mutable->cache, &find) < 0)) {
162 RPEDEBUG("Cache lookup failed");
163 return CACHE_ERROR;
164 }
165 if (!c) return CACHE_MISS;
166
167 fr_heap_extract(&driver->mutable->heap, c);
168 fr_rb_delete(driver->mutable->cache, c);
169 talloc_free(c);
170
171 return CACHE_OK;
172}
173
174/** Insert a new entry into the data store
175 *
176 * @note handle not used except for sanity checks.
177 *
178 * @copydetails cache_entry_insert_t
179 */
181 request_t *request, void *handle,
182 rlm_cache_entry_t const *c)
183{
184 cache_status_t status;
185
186 rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
187
188 fr_assert(handle == request);
189
190 if (!request) return CACHE_ERROR;
191
192 /*
193 * Allow overwriting
194 */
195 if (fr_rb_insert(driver->mutable->cache, c) != 0) {
196 status = cache_entry_expire(config, instance, request, handle, &c->key);
197 if ((status != CACHE_OK) && !fr_cond_assert(0)) return CACHE_ERROR;
198
199 if (fr_rb_insert(driver->mutable->cache, c) != 0) {
200 RERROR("Failed adding entry");
201
202 return CACHE_ERROR;
203 }
204 }
205
206 if (fr_heap_insert(&driver->mutable->heap, UNCONST(rlm_cache_entry_t *, c)) < 0) {
207 fr_rb_delete(driver->mutable->cache, c);
208 RERROR("Failed adding entry to expiry heap");
209
210 return CACHE_ERROR;
211 }
212
213 return CACHE_OK;
214}
215
216/** Update the TTL of an entry
217 *
218 * @note handle not used except for sanity checks.
219 *
220 * @copydetails cache_entry_set_ttl_t
221 */
223 request_t *request, UNUSED void *handle,
225{
226 rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
227
228#ifdef NDEBUG
229 if (!request) return CACHE_ERROR;
230#endif
231
232 if (!fr_cond_assert(fr_heap_extract(&driver->mutable->heap, c) == 0)) {
233 RERROR("Entry not in heap");
234 return CACHE_ERROR;
235 }
236
237 if (fr_heap_insert(&driver->mutable->heap, c) < 0) {
238 fr_rb_delete(driver->mutable->cache, c); /* make sure we don't leak entries... */
239 RERROR("Failed updating entry TTL. Entry was forcefully expired");
240 return CACHE_ERROR;
241 }
242 return CACHE_OK;
243}
244
245/** Return the number of entries in the cache
246 *
247 * @note handle not used except for sanity checks.
248 *
249 * @copydetails cache_entry_count_t
250 */
251static uint64_t cache_entry_count(UNUSED rlm_cache_config_t const *config, void *instance,
252 request_t *request, UNUSED void *handle)
253{
254 rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
255
256 if (!request) return 0; /* can't return an error due to signed */
257
258 return fr_rb_num_elements(driver->mutable->cache);
259}
260
261/** Lock the rbtree
262 *
263 * @note handle not used except for sanity checks.
264 *
265 * @copydetails cache_acquire_t
266 */
267static int cache_acquire(void **handle, UNUSED rlm_cache_config_t const *config, void *instance,
268 request_t *request)
269{
270 rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
271
272 pthread_mutex_lock(&driver->mutable->mutex);
273
274 *handle = request; /* handle is unused, this is just for sanity checking */
275
276 RDEBUG3("Mutex acquired");
277
278 return 0;
279}
280
281/** Release an entry unlocking any mutexes
282 *
283 * @note handle not used except for sanity checks.
284 *
285 * @copydetails cache_release_t
286 */
287static void cache_release(UNUSED rlm_cache_config_t const *config, void *instance, request_t *request,
289{
290 rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
291
292 pthread_mutex_unlock(&driver->mutable->mutex);
293
294 RDEBUG3("Mutex released");
295}
296
297/** Cleanup a cache_rbtree instance
298 *
299 */
300static int mod_detach(module_detach_ctx_t const *mctx)
301{
302 rlm_cache_rbtree_t *driver = talloc_get_type_abort(mctx->mi->data, rlm_cache_rbtree_t);
303 rlm_cache_rbtree_mutable_t *mutable = driver->mutable;
304
305 if (mutable->cache) {
307 void *data;
308
309 for (data = fr_rb_iter_init_inorder(mutable->cache, &iter);
310 data;
311 data = fr_rb_iter_next_inorder(mutable->cache, &iter)) {
312 fr_rb_iter_delete_inorder(mutable->cache, &iter);
314 }
315 }
316
317 pthread_mutex_destroy(&mutable->mutex);
318
319 TALLOC_FREE(driver->mutable);
320
321 return 0;
322}
323
324/** Create a new cache_rbtree instance
325 *
326 * @param[in] mctx Data required for instantiation.
327 * @return
328 * - 0 on success.
329 * - -1 on failure.
330 */
331static int mod_instantiate(module_inst_ctx_t const *mctx)
332{
333 rlm_cache_rbtree_t *driver = talloc_get_type_abort(mctx->mi->data, rlm_cache_rbtree_t);
335 int ret;
336
337 MEM(mutable = talloc_zero(NULL, rlm_cache_rbtree_mutable_t));
338
339 /*
340 * The cache.
341 */
342 mutable->cache = fr_rb_inline_talloc_alloc(mutable, rlm_cache_rb_entry_t, node, cache_entry_cmp, NULL);
343 if (!mutable->cache) {
344 ERROR("Failed to create cache");
345 error:
346 talloc_free(mutable);
347 return -1;
348 }
349
350 /*
351 * The heap of entries to expire.
352 */
353 mutable->heap = fr_heap_talloc_alloc(mutable, cache_heap_cmp, rlm_cache_rb_entry_t, heap_id, 0);
354 if (!mutable->heap) {
355 ERROR("Failed to create heap for the cache");
356 goto error;
357 }
358
359 if ((ret = pthread_mutex_init(&mutable->mutex, NULL)) < 0) {
360 ERROR("Failed initializing mutex: %s", fr_syserror(ret));
361 goto error;
362 }
363
364 driver->mutable = mutable;
365
366 return 0;
367}
368
371 .common = {
372 .magic = MODULE_MAGIC_INIT,
373 .name = "cache_rbtree",
375 .detach = mod_detach,
376 .inst_size = sizeof(rlm_cache_rbtree_t),
377 .inst_type = "rlm_cache_rbtree_t",
378 },
379 .alloc = cache_entry_alloc,
380
381 .find = cache_entry_find,
382 .insert = cache_entry_insert,
383 .expire = cache_entry_expire,
384 .set_ttl = cache_entry_set_ttl,
385 .count = cache_entry_count,
386
387 .acquire = cache_acquire,
388 .release = cache_release,
389};
#define UNCONST(_type, _ptr)
Remove const qualification from a pointer.
Definition build.h:186
#define unlikely(_x)
Definition build.h:455
#define UNUSED
Definition build.h:384
#define MEMCMP_FIELDS(_a, _b, _field, _len_field)
Return the comparison of two opaque fields of a structure.
Definition build.h:178
#define fr_cond_assert(_x)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition debug.h:131
#define MEM(x)
Definition debug.h:36
#define ERROR(fmt,...)
Definition dhcpclient.c:40
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition dl_module.h:63
int fr_heap_insert(fr_heap_t **hp, void *data)
Insert a new element into the heap.
Definition heap.c:149
int fr_heap_extract(fr_heap_t **hp, void *data)
Remove a node from the heap.
Definition heap.c:259
unsigned int fr_heap_index_t
Definition heap.h:82
static void * fr_heap_peek(fr_heap_t *h)
Return the item from the top of the heap but don't pop it.
Definition heap.h:138
#define fr_heap_talloc_alloc(_ctx, _cmp, _talloc_type, _field, _init)
Creates a heap that verifies elements are of a specific talloc type.
Definition heap.h:117
The main heap structure.
Definition heap.h:68
talloc_free(hp)
#define RDEBUG3(fmt,...)
Definition log.h:360
#define RERROR(fmt,...)
Definition log.h:315
#define RPEDEBUG(fmt,...)
Definition log.h:393
fr_cmp_ret_t
Result of an ordering comparison.
Definition misc.h:50
module_instance_t * mi
Module instance to detach.
Definition module_ctx.h:57
module_instance_t * mi
Instance of the module being instantiated.
Definition module_ctx.h:51
Temporary structure to hold arguments for detach calls.
Definition module_ctx.h:56
Temporary structure to hold arguments for instantiation calls.
Definition module_ctx.h:50
static const conf_parser_t config[]
Definition base.c:162
#define fr_assert(_expr)
Definition rad_assert.h:37
uint32_t fr_rb_num_elements(fr_rb_tree_t *tree)
Return how many nodes there are in a tree.
Definition rb.c:807
int fr_rb_find(void **found, fr_rb_tree_t const *tree, void const *data)
Find an element in the tree, returning the data, not the node.
Definition rb.c:586
void * fr_rb_iter_init_inorder(fr_rb_tree_t *tree, fr_rb_iter_inorder_t *iter)
Initialise an in-order iterator.
Definition rb.c:850
void fr_rb_iter_delete_inorder(fr_rb_tree_t *tree, fr_rb_iter_inorder_t *iter)
Remove the current node from the tree.
Definition rb.c:925
int fr_rb_delete(fr_rb_tree_t *tree, void const *data)
Remove node and free data (if a free function was specified)
Definition rb.c:767
int fr_rb_insert(fr_rb_tree_t *tree, void const *data)
Insert data into a tree.
Definition rb.c:637
void * fr_rb_iter_next_inorder(UNUSED fr_rb_tree_t *tree, fr_rb_iter_inorder_t *iter)
Return the next node.
Definition rb.c:876
#define fr_rb_inline_talloc_alloc(_ctx, _type, _field, _data_cmp, _data_free)
Allocs a red black that verifies elements are of a specific talloc type.
Definition rb.h:244
Iterator structure for in-order traversal of an rbtree.
Definition rb.h:319
The main red black tree structure.
Definition rb.h:71
fr_value_box_t key
Key used to identify entry.
Definition rlm_cache.h:73
module_t common
Common fields for all loadable modules.
Definition rlm_cache.h:258
cache_status_t
Definition rlm_cache.h:39
@ CACHE_ERROR
Fatal error.
Definition rlm_cache.h:41
@ CACHE_OK
Cache entry found/updated.
Definition rlm_cache.h:42
@ CACHE_MISS
Cache entry notfound.
Definition rlm_cache.h:43
void rlm_cache_handle_t
Definition rlm_cache.h:35
fr_unix_time_t expires
When the entry expires.
Definition rlm_cache.h:76
Configuration for the rlm_cache module.
Definition rlm_cache.h:51
Definition rlm_cache.h:72
static fr_cmp_ret_t cache_entry_cmp(void const *one, void const *two)
Compare two entries by key.
static int mod_detach(module_detach_ctx_t const *mctx)
Cleanup a cache_rbtree instance.
static rlm_cache_entry_t * cache_entry_alloc(UNUSED rlm_cache_config_t const *config, UNUSED void *instance, request_t *request)
Custom allocation function for the driver.
rlm_cache_driver_t rlm_cache_rbtree
rlm_cache_rbtree_mutable_t * mutable
Mutable instance data.
fr_rb_tree_t * cache
Tree for looking up cache keys.
pthread_mutex_t mutex
Protect the tree from multiple readers/writers.
static uint64_t cache_entry_count(UNUSED rlm_cache_config_t const *config, void *instance, request_t *request, UNUSED void *handle)
Return the number of entries in the cache.
static int cache_acquire(void **handle, UNUSED rlm_cache_config_t const *config, void *instance, request_t *request)
Lock the rbtree.
fr_heap_index_t heap_id
Offset used for expiry heap.
static void cache_release(UNUSED rlm_cache_config_t const *config, void *instance, request_t *request, UNUSED rlm_cache_handle_t *handle)
Release an entry unlocking any mutexes.
static cache_status_t cache_entry_expire(UNUSED rlm_cache_config_t const *config, void *instance, request_t *request, UNUSED void *handle, fr_value_box_t const *key)
Free an entry and remove it from the data store.
fr_rb_node_t node
Entry used for lookups.
static fr_cmp_ret_t cache_heap_cmp(void const *one, void const *two)
Compare two entries by expiry time.
static cache_status_t cache_entry_find(rlm_cache_entry_t **out, UNUSED rlm_cache_config_t const *config, void *instance, request_t *request, UNUSED void *handle, fr_value_box_t const *key)
Locate a cache entry.
rlm_cache_entry_t fields
Entry data.
fr_heap_t * heap
For managing entry expiry.
static cache_status_t cache_entry_set_ttl(UNUSED rlm_cache_config_t const *config, void *instance, request_t *request, UNUSED void *handle, rlm_cache_entry_t *c)
Update the TTL of an entry.
static int mod_instantiate(module_inst_ctx_t const *mctx)
Create a new cache_rbtree instance.
static cache_status_t cache_entry_insert(rlm_cache_config_t const *config, void *instance, request_t *request, void *handle, rlm_cache_entry_t const *c)
Insert a new entry into the data store.
void * data
Module's instance data.
Definition module.h:293
module_instantiate_t instantiate
Callback to allow the module to register any per-instance resources like sockets and file handles.
Definition module.h:227
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition syserror.c:243
#define fr_unix_time_gt(_a, _b)
Definition time.h:365
static int8_t fr_unix_time_cmp(fr_unix_time_t a, fr_unix_time_t b)
Compare two fr_unix_time_t values.
Definition time.h:944
static fr_unix_time_t fr_time_to_unix_time(fr_time_t when)
Convert an fr_time_t (internal time) to our version of unix time (wallclock time)
Definition time.h:688
void fr_value_box_copy_shallow(TALLOC_CTX *ctx, fr_value_box_t *dst, fr_value_box_t const *src)
Perform a shallow copy of a value_box.
Definition value.c:4540
static fr_slen_t data
Definition value.h:1340
static size_t char ** out
Definition value.h:1030