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: ff379f92489fa847498bc47ac542beba9daf1e3d $
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_rbtree_mutable_t *mutable; //!< Mutable instance data.
40 
41 typedef struct {
42  rlm_cache_entry_t fields; //!< Entry data.
43 
44  fr_rb_node_t node; //!< Entry used for lookups.
45  fr_heap_index_t heap_id; //!< Offset used for expiry heap.
47 
48 /** Compare two entries by key
49  *
50  * There may only be one entry with the same key.
51  */
52 static int8_t cache_entry_cmp(void const *one, void const *two)
53 {
54  rlm_cache_entry_t const *a = one, *b = two;
55 
56  MEMCMP_RETURN(a, b, key.vb_strvalue, key.vb_length);
57  return 0;
58 }
59 
60 /** Compare two entries by expiry time
61  *
62  * There may be multiple entries with the same expiry time.
63  */
64 static int8_t cache_heap_cmp(void const *one, void const *two)
65 {
66  rlm_cache_entry_t const *a = one, *b = two;
67 
68  return fr_unix_time_cmp(a->expires, b->expires);
69 }
70 
71 /** Custom allocation function for the driver
72  *
73  * Allows allocation of cache entry structures with additional fields.
74  *
75  * @copydetails cache_entry_alloc_t
76  */
78  request_t *request)
79 {
81 
82  c = talloc_zero(NULL, rlm_cache_rb_entry_t);
83  if (!c) {
84  RERROR("Failed allocating cache entry");
85  return NULL;
86  }
87 
88  return (rlm_cache_entry_t *)c;
89 }
90 
91 /** Locate a cache entry
92  *
93  * @note handle not used except for sanity checks.
94  *
95  * @copydetails cache_entry_find_t
96  */
98  UNUSED rlm_cache_config_t const *config, void *instance,
99  request_t *request, UNUSED void *handle, fr_value_box_t const *key)
100 {
101  rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
102  rlm_cache_rbtree_mutable_t *mutable = driver->mutable;
103  rlm_cache_entry_t find = {};
104 
106 
107  fr_assert(mutable->cache);
108 
109  /*
110  * Clear out old entries
111  */
112  c = fr_heap_peek(mutable->heap);
113  if (c && (fr_unix_time_lt(c->expires, fr_time_to_unix_time(request->packet->timestamp)))) {
114  fr_heap_extract(&mutable->heap, c);
115  fr_rb_delete(mutable->cache, c);
116  talloc_free(c);
117  }
118 
119  fr_value_box_copy_shallow(NULL, &find.key, key);
120 
121  /*
122  * Is there an entry for this key?
123  */
124  c = fr_rb_find(mutable->cache, &find);
125  if (!c) {
126  *out = NULL;
127  return CACHE_MISS;
128  }
129  *out = c;
130 
131  return CACHE_OK;
132 }
133 
134 /** Free an entry and remove it from the data store
135  *
136  * @note handle not used except for sanity checks.
137  *
138  * @copydetails cache_entry_expire_t
139  */
141  request_t *request, UNUSED void *handle,
142  fr_value_box_t const *key)
143 {
144  rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
145  rlm_cache_entry_t find = {};
147 
148  if (!request) return CACHE_ERROR;
149 
150  fr_value_box_copy_shallow(NULL, &find.key, key);
151 
152  c = fr_rb_find(driver->mutable->cache, &find);
153  if (!c) return CACHE_MISS;
154 
155  fr_heap_extract(&driver->mutable->heap, c);
156  fr_rb_delete(driver->mutable->cache, c);
157  talloc_free(c);
158 
159  return CACHE_OK;
160 }
161 
162 /** Insert a new entry into the data store
163  *
164  * @note handle not used except for sanity checks.
165  *
166  * @copydetails cache_entry_insert_t
167  */
169  request_t *request, void *handle,
170  rlm_cache_entry_t const *c)
171 {
172  cache_status_t status;
173 
174  rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
175 
176  fr_assert(handle == request);
177 
178  if (!request) return CACHE_ERROR;
179 
180  /*
181  * Allow overwriting
182  */
183  if (!fr_rb_insert(driver->mutable->cache, c)) {
184  status = cache_entry_expire(config, instance, request, handle, &c->key);
185  if ((status != CACHE_OK) && !fr_cond_assert(0)) return CACHE_ERROR;
186 
187  if (!fr_rb_insert(driver->mutable->cache, c)) {
188  RERROR("Failed adding entry");
189 
190  return CACHE_ERROR;
191  }
192  }
193 
194  if (fr_heap_insert(&driver->mutable->heap, UNCONST(rlm_cache_entry_t *, c)) < 0) {
195  fr_rb_delete(driver->mutable->cache, c);
196  RERROR("Failed adding entry to expiry heap");
197 
198  return CACHE_ERROR;
199  }
200 
201  return CACHE_OK;
202 }
203 
204 /** Update the TTL of an entry
205  *
206  * @note handle not used except for sanity checks.
207  *
208  * @copydetails cache_entry_set_ttl_t
209  */
211  request_t *request, UNUSED void *handle,
213 {
214  rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
215 
216 #ifdef NDEBUG
217  if (!request) return CACHE_ERROR;
218 #endif
219 
220  if (!fr_cond_assert(fr_heap_extract(&driver->mutable->heap, c) == 0)) {
221  RERROR("Entry not in heap");
222  return CACHE_ERROR;
223  }
224 
225  if (fr_heap_insert(&driver->mutable->heap, c) < 0) {
226  fr_rb_delete(driver->mutable->cache, c); /* make sure we don't leak entries... */
227  RERROR("Failed updating entry TTL. Entry was forcefully expired");
228  return CACHE_ERROR;
229  }
230  return CACHE_OK;
231 }
232 
233 /** Return the number of entries in the cache
234  *
235  * @note handle not used except for sanity checks.
236  *
237  * @copydetails cache_entry_count_t
238  */
239 static uint64_t cache_entry_count(UNUSED rlm_cache_config_t const *config, void *instance,
240  request_t *request, UNUSED void *handle)
241 {
242  rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
243 
244  if (!request) return CACHE_ERROR;
245 
246  return fr_rb_num_elements(driver->mutable->cache);
247 }
248 
249 /** Lock the rbtree
250  *
251  * @note handle not used except for sanity checks.
252  *
253  * @copydetails cache_acquire_t
254  */
255 static int cache_acquire(void **handle, UNUSED rlm_cache_config_t const *config, void *instance,
256  request_t *request)
257 {
258  rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
259 
260  pthread_mutex_lock(&driver->mutable->mutex);
261 
262  *handle = request; /* handle is unused, this is just for sanity checking */
263 
264  RDEBUG3("Mutex acquired");
265 
266  return 0;
267 }
268 
269 /** Release an entry unlocking any mutexes
270  *
271  * @note handle not used except for sanity checks.
272  *
273  * @copydetails cache_release_t
274  */
275 static void cache_release(UNUSED rlm_cache_config_t const *config, void *instance, request_t *request,
276  UNUSED rlm_cache_handle_t *handle)
277 {
278  rlm_cache_rbtree_t *driver = talloc_get_type_abort(instance, rlm_cache_rbtree_t);
279 
280  pthread_mutex_unlock(&driver->mutable->mutex);
281 
282  RDEBUG3("Mutex released");
283 }
284 
285 /** Cleanup a cache_rbtree instance
286  *
287  */
288 static int mod_detach(module_detach_ctx_t const *mctx)
289 {
290  rlm_cache_rbtree_t *driver = talloc_get_type_abort(mctx->mi->data, rlm_cache_rbtree_t);
291  rlm_cache_rbtree_mutable_t *mutable = driver->mutable;
292 
293  if (mutable->cache) {
295  void *data;
296 
297  for (data = fr_rb_iter_init_inorder(&iter, mutable->cache);
298  data;
301  talloc_free(data);
302  }
303  }
304 
305  pthread_mutex_destroy(&mutable->mutex);
306 
307  TALLOC_FREE(driver->mutable);
308 
309  return 0;
310 }
311 
312 /** Create a new cache_rbtree instance
313  *
314  * @param[in] mctx Data required for instantiation.
315  * @return
316  * - 0 on success.
317  * - -1 on failure.
318  */
319 static int mod_instantiate(module_inst_ctx_t const *mctx)
320 {
321  rlm_cache_rbtree_t *driver = talloc_get_type_abort(mctx->mi->data, rlm_cache_rbtree_t);
323  int ret;
324 
325  MEM(mutable = talloc_zero(NULL, rlm_cache_rbtree_mutable_t));
326 
327  /*
328  * The cache.
329  */
330  mutable->cache = fr_rb_inline_talloc_alloc(mutable, rlm_cache_rb_entry_t, node, cache_entry_cmp, NULL);
331  if (!mutable->cache) {
332  ERROR("Failed to create cache");
333  error:
334  talloc_free(mutable);
335  goto error;
336  }
337 
338  /*
339  * The heap of entries to expire.
340  */
341  mutable->heap = fr_heap_talloc_alloc(mutable, cache_heap_cmp, rlm_cache_rb_entry_t, heap_id, 0);
342  if (!mutable->heap) {
343  ERROR("Failed to create heap for the cache");
344  goto error;
345  }
346 
347  if ((ret = pthread_mutex_init(&mutable->mutex, NULL)) < 0) {
348  ERROR("Failed initializing mutex: %s", fr_syserror(ret));
349  goto error;
350  }
351 
352  driver->mutable = mutable;
353 
354  return 0;
355 }
356 
359  .common = {
360  .magic = MODULE_MAGIC_INIT,
361  .name = "cache_rbtree",
362  .instantiate = mod_instantiate,
363  .detach = mod_detach,
364  .inst_size = sizeof(rlm_cache_rbtree_t),
365  .inst_type = "rlm_cache_rbtree_t",
366  },
367  .alloc = cache_entry_alloc,
368 
369  .find = cache_entry_find,
370  .insert = cache_entry_insert,
371  .expire = cache_entry_expire,
372  .set_ttl = cache_entry_set_ttl,
373  .count = cache_entry_count,
374 
375  .acquire = cache_acquire,
376  .release = cache_release,
377 };
#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
fr_dcursor_iter_t iter
Definition: dcursor.h:147
#define fr_cond_assert(_x)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition: debug.h:139
#define ERROR(fmt,...)
Definition: dhcpclient.c:41
#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: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)
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:183
void fr_rb_iter_delete_inorder(fr_rb_iter_inorder_t *iter)
Remove the current node from the tree.
Definition: rb.c:898
void * fr_rb_iter_next_inorder(fr_rb_iter_inorder_t *iter)
Return the next node.
Definition: rb.c:850
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:824
uint32_t fr_rb_num_elements(fr_rb_tree_t *tree)
#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
bool fr_rb_insert(fr_rb_tree_t *tree, void const *data)
bool fr_rb_delete(fr_rb_tree_t *tree, void const *data)
void * fr_rb_find(fr_rb_tree_t const *tree, void const *data)
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
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 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_heap_t * heap
For managing entry expiry.
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.
void * data
Module's instance data.
Definition: module.h:271
fr_assert(0)
MEM(pair_append_request(&vp, attr_eap_aka_sim_identity) >=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: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
#define fr_unix_time_lt(_a, _b)
Definition: time.h:367
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:3834
static fr_slen_t data
Definition: value.h:1265
static size_t char ** out
Definition: value.h:997