The FreeRADIUS server  $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
rlm_cache_rbtree.c
Go to the documentation of this file.
1 /*
2  * This program is 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: 4838e824d1fc721f0f4f478f972840bcb0898121 $
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/heap.h>
26 #include <freeradius-devel/util/debug.h>
27 #include <freeradius-devel/util/value.h>
28 #include "../../rlm_cache.h"
29 
30 typedef struct {
31  fr_rb_tree_t *cache; //!< Tree for looking up cache keys.
32  fr_heap_t *heap; //!< For managing entry expiry.
33 
34  pthread_mutex_t mutex; //!< Protect the tree from multiple readers/writers.
36 
37 typedef struct {
38  rlm_cache_entry_t fields; //!< Entry data.
39 
40  fr_rb_node_t node; //!< Entry used for lookups.
41  fr_heap_index_t heap_id; //!< Offset used for expiry heap.
43 
44 /** Compare two entries by key
45  *
46  * There may only be one entry with the same key.
47  */
48 static int8_t cache_entry_cmp(void const *one, void const *two)
49 {
50  rlm_cache_entry_t const *a = one, *b = two;
51 
52  MEMCMP_RETURN(a, b, key.vb_strvalue, key.vb_length);
53  return 0;
54 }
55 
56 /** Compare two entries by expiry time
57  *
58  * There may be multiple entries with the same expiry time.
59  */
60 static int8_t cache_heap_cmp(void const *one, void const *two)
61 {
62  rlm_cache_entry_t const *a = one, *b = two;
63 
64  return fr_unix_time_cmp(a->expires, b->expires);
65 }
66 
67 /** Cleanup a cache_rbtree instance
68  *
69  */
70 static int mod_detach(module_detach_ctx_t const *mctx)
71 {
72  rlm_cache_rbtree_t *driver = talloc_get_type_abort(mctx->inst->data, rlm_cache_rbtree_t);
73 
74  if (driver->cache) {
76  void *data;
77 
78  for (data = fr_rb_iter_init_inorder(&iter, driver->cache);
79  data;
80  data = fr_rb_iter_next_inorder(&iter)) {
83  }
84  }
85 
86  pthread_mutex_destroy(&driver->mutex);
87 
88  return 0;
89 }
90 
91 /** Create a new cache_rbtree instance
92  *
93  * @param[in] mctx Data required for instantiation.
94  * @return
95  * - 0 on success.
96  * - -1 on failure.
97  */
98 static int mod_instantiate(module_inst_ctx_t const *mctx)
99 {
100  rlm_cache_rbtree_t *driver = talloc_get_type_abort(mctx->inst->data, rlm_cache_rbtree_t);
101  int ret;
102 
103  /*
104  * The cache.
105  */
107  if (!driver->cache) {
108  ERROR("Failed to create cache");
109  return -1;
110  }
111 
112  /*
113  * The heap of entries to expire.
114  */
115  driver->heap = fr_heap_talloc_alloc(driver, cache_heap_cmp, rlm_cache_rb_entry_t, heap_id, 0);
116  if (!driver->heap) {
117  ERROR("Failed to create heap for the cache");
118  return -1;
119  }
120 
121  if ((ret = pthread_mutex_init(&driver->mutex, NULL)) < 0) {
122  ERROR("Failed initializing mutex: %s", fr_syserror(ret));
123  return -1;
124  }
125 
126  return 0;
127 }
128 
129 /** Custom allocation function for the driver
130  *
131  * Allows allocation of cache entry structures with additional fields.
132  *
133  * @copydetails cache_entry_alloc_t
134  */
136  request_t *request)
137 {
139 
140  c = talloc_zero(NULL, rlm_cache_rb_entry_t);
141  if (!c) {
142  RERROR("Failed allocating cache entry");
143  return NULL;
144  }
145 
146  return (rlm_cache_entry_t *)c;
147 }
148 
149 /** Locate a cache entry
150  *
151  * @note handle not used except for sanity checks.
152  *
153  * @copydetails cache_entry_find_t
154  */
156  UNUSED rlm_cache_config_t const *config, void *instance,
157  request_t *request, UNUSED void *handle, fr_value_box_t const *key)
158 {
159  rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
160  rlm_cache_entry_t find = {};
161 
163 
164  fr_assert(driver->cache);
165 
166  /*
167  * Clear out old entries
168  */
169  c = fr_heap_peek(driver->heap);
170  if (c && (fr_unix_time_lt(c->expires, fr_time_to_unix_time(request->packet->timestamp)))) {
171  fr_heap_extract(&driver->heap, c);
172  fr_rb_delete(driver->cache, c);
173  talloc_free(c);
174  }
175 
176  fr_value_box_copy_shallow(NULL, &find.key, key);
177 
178  /*
179  * Is there an entry for this key?
180  */
181  c = fr_rb_find(driver->cache, &find);
182  if (!c) {
183  *out = NULL;
184  return CACHE_MISS;
185  }
186  *out = c;
187 
188  return CACHE_OK;
189 }
190 
191 /** Free an entry and remove it from the data store
192  *
193  * @note handle not used except for sanity checks.
194  *
195  * @copydetails cache_entry_expire_t
196  */
198  request_t *request, UNUSED void *handle,
199  fr_value_box_t const *key)
200 {
201  rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
202  rlm_cache_entry_t find = {};
204 
205  if (!request) return CACHE_ERROR;
206 
207  fr_value_box_copy_shallow(NULL, &find.key, key);
208 
209  c = fr_rb_find(driver->cache, &find);
210  if (!c) return CACHE_MISS;
211 
212  fr_heap_extract(&driver->heap, c);
213  fr_rb_delete(driver->cache, c);
214  talloc_free(c);
215 
216  return CACHE_OK;
217 }
218 
219 /** Insert a new entry into the data store
220  *
221  * @note handle not used except for sanity checks.
222  *
223  * @copydetails cache_entry_insert_t
224  */
226  request_t *request, void *handle,
227  rlm_cache_entry_t const *c)
228 {
229  cache_status_t status;
230 
231  rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
232 
233  fr_assert(handle == request);
234 
235  if (!request) return CACHE_ERROR;
236 
237  /*
238  * Allow overwriting
239  */
240  if (!fr_rb_insert(driver->cache, c)) {
241  status = cache_entry_expire(config, instance, request, handle, &c->key);
242  if ((status != CACHE_OK) && !fr_cond_assert(0)) return CACHE_ERROR;
243 
244  if (!fr_rb_insert(driver->cache, c)) {
245  RERROR("Failed adding entry");
246 
247  return CACHE_ERROR;
248  }
249  }
250 
251  if (fr_heap_insert(&driver->heap, UNCONST(rlm_cache_entry_t *, c)) < 0) {
252  fr_rb_delete(driver->cache, c);
253  RERROR("Failed adding entry to expiry heap");
254 
255  return CACHE_ERROR;
256  }
257 
258  return CACHE_OK;
259 }
260 
261 /** Update the TTL of an entry
262  *
263  * @note handle not used except for sanity checks.
264  *
265  * @copydetails cache_entry_set_ttl_t
266  */
268  request_t *request, UNUSED void *handle,
270 {
271  rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
272 
273 #ifdef NDEBUG
274  if (!request) return CACHE_ERROR;
275 #endif
276 
277  if (!fr_cond_assert(fr_heap_extract(&driver->heap, c) == 0)) {
278  RERROR("Entry not in heap");
279  return CACHE_ERROR;
280  }
281 
282  if (fr_heap_insert(&driver->heap, c) < 0) {
283  fr_rb_delete(driver->cache, c); /* make sure we don't leak entries... */
284  RERROR("Failed updating entry TTL. Entry was forcefully expired");
285  return CACHE_ERROR;
286  }
287  return CACHE_OK;
288 }
289 
290 /** Return the number of entries in the cache
291  *
292  * @note handle not used except for sanity checks.
293  *
294  * @copydetails cache_entry_count_t
295  */
296 static uint64_t cache_entry_count(UNUSED rlm_cache_config_t const *config, void *instance,
297  request_t *request, UNUSED void *handle)
298 {
299  rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
300 
301  if (!request) return CACHE_ERROR;
302 
303  return fr_rb_num_elements(driver->cache);
304 }
305 
306 /** Lock the rbtree
307  *
308  * @note handle not used except for sanity checks.
309  *
310  * @copydetails cache_acquire_t
311  */
312 static int cache_acquire(void **handle, UNUSED rlm_cache_config_t const *config, void *instance,
313  request_t *request)
314 {
315  rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
316 
317  pthread_mutex_lock(&driver->mutex);
318 
319  *handle = request; /* handle is unused, this is just for sanity checking */
320 
321  RDEBUG3("Mutex acquired");
322 
323  return 0;
324 }
325 
326 /** Release an entry unlocking any mutexes
327  *
328  * @note handle not used except for sanity checks.
329  *
330  * @copydetails cache_release_t
331  */
332 static void cache_release(UNUSED rlm_cache_config_t const *config, void *instance, request_t *request,
333  UNUSED rlm_cache_handle_t *handle)
334 {
335  rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
336 
337  pthread_mutex_unlock(&driver->mutex);
338 
339  RDEBUG3("Mutex released");
340 }
341 
344  .common = {
345  .magic = MODULE_MAGIC_INIT,
346  .name = "cache_rbtree",
347  .instantiate = mod_instantiate,
348  .detach = mod_detach,
349  .inst_size = sizeof(rlm_cache_rbtree_t),
350  .inst_type = "rlm_cache_rbtree_t",
351  },
352  .alloc = cache_entry_alloc,
353 
354  .find = cache_entry_find,
355  .insert = cache_entry_insert,
356  .expire = cache_entry_expire,
357  .set_ttl = cache_entry_set_ttl,
358  .count = cache_entry_count,
359 
360  .acquire = cache_acquire,
361  .release = cache_release,
362 };
#define UNCONST(_type, _ptr)
Remove const qualification from a pointer.
Definition: build.h:165
#define MEMCMP_RETURN(_a, _b, _field, _len_field)
Return if the contents of the specified field is not identical between the specified structures.
Definition: build.h:154
#define UNUSED
Definition: build.h:313
#define fr_cond_assert(_x)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition: debug.h:137
#define ERROR(fmt,...)
Definition: dhcpclient.c:41
void *_CONST data
Module instance's parsed configuration.
Definition: dl_module.h:165
#define MODULE_MAGIC_INIT
Stop people using different module/library/server versions together.
Definition: dl_module.h:65
dl_module_inst_t const * inst
Definition: dl_module.h:87
int fr_heap_insert(fr_heap_t **hp, void *data)
Insert a new element into the heap.
Definition: heap.c:146
int fr_heap_extract(fr_heap_t **hp, void *data)
Remove a node from the heap.
Definition: heap.c:239
unsigned int fr_heap_index_t
Definition: heap.h:80
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:136
#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:115
The main heap structure.
Definition: heap.h:66
#define RDEBUG3(fmt,...)
Definition: log.h:343
#define RERROR(fmt,...)
Definition: log.h:298
talloc_free(reap)
dl_module_inst_t const * inst
Dynamic loader API handle for the module.
Definition: module_ctx.h:52
Temporary structure to hold arguments for instantiation calls.
Definition: module_ctx.h:51
static const conf_parser_t config[]
Definition: base.c:188
uint32_t fr_rb_num_elements(fr_rb_tree_t *tree)
Return how many nodes there are in a tree.
Definition: rb.c:775
void fr_rb_iter_delete_inorder(fr_rb_iter_inorder_t *iter)
Remove the current node from the tree.
Definition: rb.c:892
void * fr_rb_iter_next_inorder(fr_rb_iter_inorder_t *iter)
Return the next node.
Definition: rb.c:844
bool fr_rb_insert(fr_rb_tree_t *tree, void const *data)
Insert data into a tree.
Definition: rb.c:624
bool 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:736
void * fr_rb_iter_init_inorder(fr_rb_iter_inorder_t *iter, fr_rb_tree_t *tree)
Initialise an in-order iterator.
Definition: rb.c:818
void * fr_rb_find(fr_rb_tree_t const *tree, void const *data)
Find an element in the tree, returning the data, not the node.
Definition: rb.c:576
#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:246
Iterator structure for in-order traversal of an rbtree.
Definition: rb.h:321
The main red black tree structure.
Definition: rb.h:73
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 int mod_detach(module_detach_ctx_t const *mctx)
Cleanup a cache_rbtree instance.
rlm_cache_driver_t rlm_cache_rbtree
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.
pthread_mutex_t mutex
Protect the tree from multiple readers/writers.
static int8_t cache_heap_cmp(void const *one, void const *two)
Compare two entries by expiry time.
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 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_rb_tree_t * cache
Tree for looking up cache keys.
static int8_t cache_entry_cmp(void const *one, void const *two)
Compare two entries by key.
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.
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.
fr_heap_t * heap
For managing entry expiry.
fr_assert(0)
char const * fr_syserror(int num)
Guaranteed to be thread-safe version of strerror.
Definition: syserror.c:243
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:942
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:686
#define fr_unix_time_lt(_a, _b)
Definition: time.h:365
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:3783
static fr_slen_t data
Definition: value.h:1259
static size_t char ** out
Definition: value.h:984