Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2015, Digium, Inc.
*
* Joshua Colp <jcolp@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*!
* \file
*
* \brief Sorcery Memory Cache Object Wizard
*
* \author Joshua Colp <jcolp@digium.com>
*/
/*** MODULEINFO
<support_level>core</support_level>
***/
#include "asterisk.h"
ASTERISK_REGISTER_FILE()
#include "asterisk/module.h"
#include "asterisk/sorcery.h"
#include "asterisk/astobj2.h"
#include "asterisk/sched.h"
#include "asterisk/test.h"
#include "asterisk/heap.h"
/*! \brief Structure for storing a memory cache */
struct sorcery_memory_cache {
/*! \brief The name of the memory cache */
char *name;
/*! \brief Objects in the cache */
struct ao2_container *objects;
/*! \brief The maximum number of objects permitted in the cache, 0 if no limit */
unsigned int maximum_objects;
/*! \brief The maximum time (in seconds) an object will stay in the cache, 0 if no limit */
unsigned int object_lifetime_maximum;
/*! \brief The amount of time (in seconds) before an object is marked as stale, 0 if disabled */
unsigned int object_lifetime_stale;
/*! \brief Whether objects are prefetched from normal storage at load time, 0 if disabled */
unsigned int prefetch;
/** \brief Whether all objects are expired when the object type is reloaded, 0 if disabled */
unsigned int expire_on_reload;
/*! \brief Heap of cached objects. Oldest object is at the top. */
struct ast_heap *object_heap;
/*! \brief Scheduler item for expiring oldest object. */
int expire_id;
#ifdef TEST_FRAMEWORK
/*! \brief Variable used to indicate we should notify a test when we reach empty */
unsigned int cache_notify;
/*! \brief Mutex lock used for signaling when the cache has reached empty */
ast_mutex_t lock;
/*! \brief Condition used for signaling when the cache has reached empty */
ast_cond_t cond;
/*! \brief Variable that is set when the cache has reached empty */
unsigned int cache_completed;
#endif
};
/*! \brief Structure for stored a cached object */
struct sorcery_memory_cached_object {
/*! \brief The cached object */
void *object;
/*! \brief The time at which the object was created */
struct timeval created;
/*! \brief index required by heap */
ssize_t __heap_index;
/*! \brief scheduler id of stale update task */
int stale_update_sched_id;
};
static void *sorcery_memory_cache_open(const char *data);
static int sorcery_memory_cache_create(const struct ast_sorcery *sorcery, void *data, void *object);
static void sorcery_memory_cache_load(void *data, const struct ast_sorcery *sorcery, const char *type);
static void sorcery_memory_cache_reload(void *data, const struct ast_sorcery *sorcery, const char *type);
static void *sorcery_memory_cache_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type,
const char *id);
static int sorcery_memory_cache_delete(const struct ast_sorcery *sorcery, void *data, void *object);
static void sorcery_memory_cache_close(void *data);
static struct ast_sorcery_wizard memory_cache_object_wizard = {
.name = "memory_cache",
.open = sorcery_memory_cache_open,
.create = sorcery_memory_cache_create,
.update = sorcery_memory_cache_create,
.delete = sorcery_memory_cache_delete,
.load = sorcery_memory_cache_load,
.reload = sorcery_memory_cache_reload,
.retrieve_id = sorcery_memory_cache_retrieve_id,
.close = sorcery_memory_cache_close,
};
/*! \brief The bucket size for the container of caches */
#define CACHES_CONTAINER_BUCKET_SIZE 53
/*! \brief The default bucket size for the container of objects in the cache */
#define CACHE_CONTAINER_BUCKET_SIZE 53
/*! \brief Height of heap for cache object heap. Allows 31 initial objects */
#define CACHE_HEAP_INIT_HEIGHT 5
/*! \brief Container of created caches */
static struct ao2_container *caches;
/*! \brief Scheduler for cache management */
static struct ast_sched_context *sched;
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#define STALE_UPDATE_THREAD_ID 0x5EED1E55
AST_THREADSTORAGE(stale_update_id_storage);
static int is_stale_update(void)
{
uint32_t *stale_update_thread_id;
stale_update_thread_id = ast_threadstorage_get(&stale_update_id_storage,
sizeof(*stale_update_thread_id));
if (!stale_update_thread_id) {
return 0;
}
return *stale_update_thread_id == STALE_UPDATE_THREAD_ID;
}
static void start_stale_update(void)
{
uint32_t *stale_update_thread_id;
stale_update_thread_id = ast_threadstorage_get(&stale_update_id_storage,
sizeof(*stale_update_thread_id));
if (!stale_update_thread_id) {
ast_log(LOG_ERROR, "Could not set stale update ID for sorcery memory cache thread\n");
return;
}
*stale_update_thread_id = STALE_UPDATE_THREAD_ID;
}
static void end_stale_update(void)
{
uint32_t *stale_update_thread_id;
stale_update_thread_id = ast_threadstorage_get(&stale_update_id_storage,
sizeof(*stale_update_thread_id));
if (!stale_update_thread_id) {
ast_log(LOG_ERROR, "Could not set stale update ID for sorcery memory cache thread\n");
return;
}
*stale_update_thread_id = 0;
}
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
/*!
* \internal
* \brief Hashing function for the container holding caches
*
* \param obj A sorcery memory cache or name of one
* \param flags Hashing flags
*
* \return The hash of the memory cache name
*/
static int sorcery_memory_cache_hash(const void *obj, int flags)
{
const struct sorcery_memory_cache *cache = obj;
const char *name = obj;
int hash;
switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
default:
case OBJ_SEARCH_OBJECT:
name = cache->name;
/* Fall through */
case OBJ_SEARCH_KEY:
hash = ast_str_hash(name);
break;
case OBJ_SEARCH_PARTIAL_KEY:
/* Should never happen in hash callback. */
ast_assert(0);
hash = 0;
break;
}
return hash;
}
/*!
* \internal
* \brief Comparison function for the container holding caches
*
* \param obj A sorcery memory cache
* \param arg A sorcery memory cache, or name of one
* \param flags Comparison flags
*
* \retval CMP_MATCH if the name is the same
* \retval 0 if the name does not match
*/
static int sorcery_memory_cache_cmp(void *obj, void *arg, int flags)
{
const struct sorcery_memory_cache *left = obj;
const struct sorcery_memory_cache *right = arg;
const char *right_name = arg;
int cmp;
switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
default:
case OBJ_SEARCH_OBJECT:
right_name = right->name;
/* Fall through */
case OBJ_SEARCH_KEY:
cmp = strcmp(left->name, right_name);
break;
case OBJ_SEARCH_PARTIAL_KEY:
cmp = strncmp(left->name, right_name, strlen(right_name));
break;
}
return cmp ? 0 : CMP_MATCH;
}
/*!
* \internal
* \brief Hashing function for the container holding cached objects
*
* \param obj A cached object or id of one
* \param flags Hashing flags
*
* \return The hash of the cached object id
*/
static int sorcery_memory_cached_object_hash(const void *obj, int flags)
{
const struct sorcery_memory_cached_object *cached = obj;
const char *name = obj;
int hash;
switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
default:
case OBJ_SEARCH_OBJECT:
name = ast_sorcery_object_get_id(cached->object);
/* Fall through */
case OBJ_SEARCH_KEY:
hash = ast_str_hash(name);
break;
case OBJ_SEARCH_PARTIAL_KEY:
/* Should never happen in hash callback. */
ast_assert(0);
hash = 0;
break;
}
return hash;
}
/*!
* \internal
* \brief Comparison function for the container holding cached objects
*
* \param obj A cached object
* \param arg A cached object, or id of one
* \param flags Comparison flags
*
* \retval CMP_MATCH if the id is the same
* \retval 0 if the id does not match
*/
static int sorcery_memory_cached_object_cmp(void *obj, void *arg, int flags)
{
struct sorcery_memory_cached_object *left = obj;
struct sorcery_memory_cached_object *right = arg;
const char *right_name = arg;
int cmp;
switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
default:
case OBJ_SEARCH_OBJECT:
right_name = ast_sorcery_object_get_id(right->object);
/* Fall through */
case OBJ_SEARCH_KEY:
cmp = strcmp(ast_sorcery_object_get_id(left->object), right_name);
break;
case OBJ_SEARCH_PARTIAL_KEY:
cmp = strncmp(ast_sorcery_object_get_id(left->object), right_name, strlen(right_name));
break;
}
return cmp ? 0 : CMP_MATCH;
}
/*!
* \internal
* \brief Destructor function for a sorcery memory cache
*
* \param obj A sorcery memory cache
*/
static void sorcery_memory_cache_destructor(void *obj)
{
struct sorcery_memory_cache *cache = obj;
ast_free(cache->name);
ao2_cleanup(cache->objects);
if (cache->object_heap) {
ast_heap_destroy(cache->object_heap);
}
}
/*!
* \internal
* \brief Destructor function for sorcery memory cached objects
*
* \param obj A sorcery memory cached object
*/
static void sorcery_memory_cached_object_destructor(void *obj)
{
struct sorcery_memory_cached_object *cached = obj;
ao2_cleanup(cached->object);
}
static int schedule_cache_expiration(struct sorcery_memory_cache *cache);
/*!
* \internal
* \brief Remove an object from the cache.
*
* This removes the item from both the hashtable and the heap.
*
* \pre cache->objects is write-locked
*
* \param cache The cache from which the object is being removed.
* \param id The sorcery object id of the object to remove.
* \param reschedule Reschedule cache expiration if this was the oldest object.
*
* \retval 0 Success
* \retval non-zero Failure
*/
static int remove_from_cache(struct sorcery_memory_cache *cache, const char *id, int reschedule)
{
struct sorcery_memory_cached_object *hash_object;
struct sorcery_memory_cached_object *oldest_object;
struct sorcery_memory_cached_object *heap_object;
hash_object = ao2_find(cache->objects, id,
OBJ_SEARCH_KEY | OBJ_UNLINK | OBJ_NOLOCK);
if (!hash_object) {
return -1;
}
oldest_object = ast_heap_peek(cache->object_heap, 1);
heap_object = ast_heap_remove(cache->object_heap, hash_object);
ast_assert(heap_object == hash_object);
ao2_ref(hash_object, -1);
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
if (reschedule && (oldest_object == heap_object)) {
schedule_cache_expiration(cache);
}
return 0;
}
/*!
* \internal
* \brief Scheduler callback invoked to expire old objects
*
* \param data The opaque callback data (in our case, the memory cache)
*/
static int expire_objects_from_cache(const void *data)
{
struct sorcery_memory_cache *cache = (struct sorcery_memory_cache *)data;
struct sorcery_memory_cached_object *cached;
ao2_wrlock(cache->objects);
cache->expire_id = -1;
/* This is an optimization for objects which have been cached close to eachother */
while ((cached = ast_heap_peek(cache->object_heap, 1))) {
int expiration;
expiration = ast_tvdiff_ms(ast_tvadd(cached->created, ast_samp2tv(cache->object_lifetime_maximum, 1)), ast_tvnow());
/* If the current oldest object has not yet expired stop and reschedule for it */
if (expiration > 0) {
break;
}
remove_from_cache(cache, ast_sorcery_object_get_id(cached->object), 0);
}
schedule_cache_expiration(cache);
ao2_unlock(cache->objects);
ao2_ref(cache, -1);
return 0;
}
/*!
* \internal
* \brief Schedule a callback for cached object expiration.
*
* \pre cache->objects is write-locked
*
* \param cache The cache that is having its callback scheduled.
*
* \retval 0 success
* \retval -1 failure
*/
static int schedule_cache_expiration(struct sorcery_memory_cache *cache)
{
struct sorcery_memory_cached_object *cached;
int expiration = 0;
if (!cache->object_lifetime_maximum) {
return 0;
}
if (cache->expire_id != -1) {
/* If we can't unschedule this expiration then it is currently attempting to run,
* so let it run - it just means that it'll be the one scheduling instead of us.
*/
if (ast_sched_del(sched, cache->expire_id)) {
return 0;
}
/* Since it successfully cancelled we need to drop the ref to the cache it had */
ao2_ref(cache, -1);
cache->expire_id = -1;
}
cached = ast_heap_peek(cache->object_heap, 1);
if (!cached) {
#ifdef TEST_FRAMEWORK
ast_mutex_lock(&cache->lock);
cache->cache_completed = 1;
ast_cond_signal(&cache->cond);
ast_mutex_unlock(&cache->lock);
#endif
return 0;
}
expiration = MAX(ast_tvdiff_ms(ast_tvadd(cached->created, ast_samp2tv(cache->object_lifetime_maximum, 1)), ast_tvnow()),
1);
cache->expire_id = ast_sched_add(sched, expiration, expire_objects_from_cache, ao2_bump(cache));
if (cache->expire_id < 0) {
ao2_ref(cache, -1);
return -1;
}
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
return 0;
}
/*!
* \internal
* \brief Remove the oldest item from the cache.
*
* \pre cache->objects is write-locked
*
* \param cache The cache from which to remove the oldest object
*
* \retval 0 Success
* \retval non-zero Failure
*/
static int remove_oldest_from_cache(struct sorcery_memory_cache *cache)
{
struct sorcery_memory_cached_object *heap_old_object;
struct sorcery_memory_cached_object *hash_old_object;
heap_old_object = ast_heap_pop(cache->object_heap);
if (!heap_old_object) {
return -1;
}
hash_old_object = ao2_find(cache->objects, heap_old_object,
OBJ_SEARCH_OBJECT | OBJ_UNLINK | OBJ_NOLOCK);
ast_assert(heap_old_object == hash_old_object);
ao2_ref(hash_old_object, -1);
schedule_cache_expiration(cache);
return 0;
}
/*!
* \internal
* \brief Add a new object to the cache.
*
* \pre cache->objects is write-locked
*
* \param cache The cache in which to add the new object
* \param cached_object The object to add to the cache
*
* \retval 0 Success
* \retval non-zero Failure
*/
static int add_to_cache(struct sorcery_memory_cache *cache,
struct sorcery_memory_cached_object *cached_object)
{
if (!ao2_link_flags(cache->objects, cached_object, OBJ_NOLOCK)) {
return -1;
}
if (ast_heap_push(cache->object_heap, cached_object)) {
ao2_find(cache->objects, cached_object,
OBJ_SEARCH_OBJECT | OBJ_UNLINK | OBJ_NODATA | OBJ_NOLOCK);
return -1;
}
if (cache->expire_id == -1) {
schedule_cache_expiration(cache);
}
return 0;
}
/*!
* \internal
* \brief Callback function to cache an object in a memory cache
*
* \param sorcery The sorcery instance
* \param data The sorcery memory cache
* \param object The object to cache
*
* \retval 0 success
* \retval -1 failure
*/
static int sorcery_memory_cache_create(const struct ast_sorcery *sorcery, void *data, void *object)
{
struct sorcery_memory_cache *cache = data;
struct sorcery_memory_cached_object *cached;
cached = ao2_alloc(sizeof(*cached), sorcery_memory_cached_object_destructor);
if (!cached) {
return -1;
}
cached->object = ao2_bump(object);
cached->created = ast_tvnow();
cached->stale_update_sched_id = -1;
/* As there is no guarantee that this won't be called by multiple threads wanting to cache
* the same object we remove any old ones, which turns this into a create/update function
* in reality. As well since there's no guarantee that the object in the cache is the same
* one here we remove any old objects using the object identifier.
*/
ao2_wrlock(cache->objects);
remove_from_cache(cache, ast_sorcery_object_get_id(object), 1);
if (cache->maximum_objects && ao2_container_count(cache->objects) >= cache->maximum_objects) {
if (remove_oldest_from_cache(cache)) {
ast_log(LOG_ERROR, "Unable to make room in cache for sorcery object '%s'.\n",
ast_sorcery_object_get_id(object));
ao2_ref(cached, -1);
ao2_unlock(cache->objects);
return -1;
}
}
if (add_to_cache(cache, cached)) {
ast_log(LOG_ERROR, "Unable to add object '%s' to the cache\n",
ast_sorcery_object_get_id(object));
ao2_ref(cached, -1);
ao2_unlock(cache->objects);
return -1;
}
ao2_unlock(cache->objects);
ao2_ref(cached, -1);
return 0;
}
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
struct stale_update_task_data {
struct ast_sorcery *sorcery;
struct sorcery_memory_cache *cache;
void *object;
};
static void stale_update_task_data_destructor(void *obj)
{
struct stale_update_task_data *task_data = obj;
ao2_cleanup(task_data->cache);
ao2_cleanup(task_data->object);
ast_sorcery_unref(task_data->sorcery);
}
static struct stale_update_task_data *stale_update_task_data_alloc(struct ast_sorcery *sorcery,
struct sorcery_memory_cache *cache, const char *type, void *object)
{
struct stale_update_task_data *task_data;
task_data = ao2_alloc_options(sizeof(*task_data), stale_update_task_data_destructor,
AO2_ALLOC_OPT_LOCK_NOLOCK);
if (!task_data) {
return NULL;
}
task_data->sorcery = ao2_bump(sorcery);
task_data->cache = ao2_bump(cache);
task_data->object = ao2_bump(object);
return task_data;
}
static int stale_item_update(const void *data)
{
struct stale_update_task_data *task_data = (struct stale_update_task_data *) data;
void *object;
start_stale_update();
object = ast_sorcery_retrieve_by_id(task_data->sorcery,
ast_sorcery_object_get_type(task_data->object),
ast_sorcery_object_get_id(task_data->object));
if (!object) {
ast_debug(1, "Backend no longer has object type '%s' ID '%s'. Removing from cache\n",
ast_sorcery_object_get_type(task_data->object),
ast_sorcery_object_get_id(task_data->object));
sorcery_memory_cache_delete(task_data->sorcery, task_data->cache,
task_data->object);
} else {
ast_debug(1, "Refreshing stale cache object type '%s' ID '%s'\n",
ast_sorcery_object_get_type(task_data->object),
ast_sorcery_object_get_id(task_data->object));
sorcery_memory_cache_create(task_data->sorcery, task_data->cache,
object);
}
ao2_ref(task_data, -1);
end_stale_update();
return 0;
}
/*!
* \internal
* \brief Callback function to retrieve an object from a memory cache
*
* \param sorcery The sorcery instance
* \param data The sorcery memory cache
* \param type The type of the object to retrieve
* \param id The id of the object to retrieve
*
* \retval non-NULL success
* \retval NULL failure
*/
static void *sorcery_memory_cache_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id)
{
struct sorcery_memory_cache *cache = data;
struct sorcery_memory_cached_object *cached;
void *object;
if (is_stale_update()) {
return NULL;
}
cached = ao2_find(cache->objects, id, OBJ_SEARCH_KEY);
if (!cached) {
return NULL;
}
if (cache->object_lifetime_stale) {
struct timeval elapsed;
elapsed = ast_tvsub(ast_tvnow(), cached->created);
if (elapsed.tv_sec > cache->object_lifetime_stale) {
ao2_lock(cached);
if (cached->stale_update_sched_id == -1) {
struct stale_update_task_data *task_data;
task_data = stale_update_task_data_alloc((struct ast_sorcery *)sorcery, cache,
type, cached->object);
if (task_data) {
ast_debug(1, "Cached sorcery object type '%s' ID '%s' is stale. Refreshing\n",
type, id);
cached->stale_update_sched_id = ast_sched_add(sched, 1, stale_item_update, task_data);
} else {
ast_log(LOG_ERROR, "Unable to update stale cached object type '%s', ID '%s'.\n",
type, id);
}
}
ao2_unlock(cached);
}
}
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
object = ao2_bump(cached->object);
ao2_ref(cached, -1);
return object;
}
/*!
* \internal
* \brief Callback function to finish configuring the memory cache and to prefetch objects
*
* \param data The sorcery memory cache
* \param sorcery The sorcery instance
* \param type The type of object being loaded
*/
static void sorcery_memory_cache_load(void *data, const struct ast_sorcery *sorcery, const char *type)
{
struct sorcery_memory_cache *cache = data;
/* If no name was explicitly specified generate one given the sorcery instance and object type */
if (ast_strlen_zero(cache->name)) {
ast_asprintf(&cache->name, "%s/%s", ast_sorcery_get_module(sorcery), type);
}
ao2_link(caches, cache);
ast_debug(1, "Memory cache '%s' associated with sorcery instance '%p' of module '%s' with object type '%s'\n",
cache->name, sorcery, ast_sorcery_get_module(sorcery), type);
}
/*!
* \internal
* \brief Callback function to expire objects from the memory cache on reload (if configured)
*
* \param data The sorcery memory cache
* \param sorcery The sorcery instance
* \param type The type of object being reloaded
*/
static void sorcery_memory_cache_reload(void *data, const struct ast_sorcery *sorcery, const char *type)
{
}
/*!
* \internal
* \brief Function used to take an unsigned integer based configuration option and parse it
*
* \param value The string value of the configuration option
* \param result The unsigned integer to place the result in
*
* \retval 0 failure
* \retval 1 success
*/
static int configuration_parse_unsigned_integer(const char *value, unsigned int *result)
{
if (ast_strlen_zero(value) || !strncmp(value, "-", 1)) {
return 0;
}
return sscanf(value, "%30u", result);
}
static int age_cmp(void *a, void *b)
{
return ast_tvcmp(((struct sorcery_memory_cached_object *) b)->created,
((struct sorcery_memory_cached_object *) a)->created);
}
/*!
* \internal
* \brief Callback function to create a new sorcery memory cache using provided configuration
*
* \param data A stringified configuration for the memory cache
*
* \retval non-NULL success
* \retval NULL failure
*/
static void *sorcery_memory_cache_open(const char *data)
{
char *options = ast_strdup(data), *option;
RAII_VAR(struct sorcery_memory_cache *, cache, NULL, ao2_cleanup);
cache = ao2_alloc_options(sizeof(*cache), sorcery_memory_cache_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
if (!cache) {
return NULL;
}
cache->expire_id = -1;
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
/* If no configuration options have been provided this memory cache will operate in a default
* configuration.
*/
while (!ast_strlen_zero(options) && (option = strsep(&options, ","))) {
char *name = strsep(&option, "="), *value = option;
if (!strcasecmp(name, "name")) {
if (ast_strlen_zero(value)) {
ast_log(LOG_ERROR, "A name must be specified for the memory cache\n");
return NULL;
}
ast_free(cache->name);
cache->name = ast_strdup(value);
} else if (!strcasecmp(name, "maximum_objects")) {
if (configuration_parse_unsigned_integer(value, &cache->maximum_objects) != 1) {
ast_log(LOG_ERROR, "Unsupported maximum objects value of '%s' used for memory cache\n",
value);
return NULL;
}
} else if (!strcasecmp(name, "object_lifetime_maximum")) {
if (configuration_parse_unsigned_integer(value, &cache->object_lifetime_maximum) != 1) {
ast_log(LOG_ERROR, "Unsupported object maximum lifetime value of '%s' used for memory cache\n",
value);
return NULL;
}
} else if (!strcasecmp(name, "object_lifetime_stale")) {
if (configuration_parse_unsigned_integer(value, &cache->object_lifetime_stale) != 1) {
ast_log(LOG_ERROR, "Unsupported object stale lifetime value of '%s' used for memory cache\n",
value);
return NULL;
}
} else if (!strcasecmp(name, "prefetch")) {
cache->prefetch = ast_true(value);
} else if (!strcasecmp(name, "expire_on_reload")) {
cache->expire_on_reload = ast_true(value);
} else {
ast_log(LOG_ERROR, "Unsupported option '%s' used for memory cache\n", name);
return NULL;
}
}
cache->objects = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_RWLOCK,
cache->maximum_objects ? cache->maximum_objects : CACHE_CONTAINER_BUCKET_SIZE,
sorcery_memory_cached_object_hash, sorcery_memory_cached_object_cmp);
if (!cache->objects) {
ast_log(LOG_ERROR, "Could not create a container to hold cached objects for memory cache\n");
return NULL;
}
cache->object_heap = ast_heap_create(CACHE_HEAP_INIT_HEIGHT, age_cmp,
offsetof(struct sorcery_memory_cached_object, __heap_index));
if (!cache->object_heap) {
ast_log(LOG_ERROR, "Could not create heap to hold cached objects\n");
return NULL;
}
/* The memory cache is not linked to the caches container until the load callback is invoked.
* Linking occurs there so an intelligent cache name can be constructed using the module of
* the sorcery instance and the specific object type if no cache name was specified as part
* of the configuration.
*/
/* This is done as RAII_VAR will drop the reference */
return ao2_bump(cache);
}
/*!
* \internal
* \brief Callback function to delete an object from a memory cache
*
* \param sorcery The sorcery instance
* \param data The sorcery memory cache
* \param object The object to cache
*
* \retval 0 success
* \retval -1 failure
*/
static int sorcery_memory_cache_delete(const struct ast_sorcery *sorcery, void *data, void *object)
{
struct sorcery_memory_cache *cache = data;
ao2_wrlock(cache->objects);
res = remove_from_cache(cache, ast_sorcery_object_get_id(object), 1);
ao2_unlock(cache->objects);
if (res) {
ast_log(LOG_ERROR, "Unable to delete object '%s' from sorcery cache\n", ast_sorcery_object_get_id(object));
}
return res;
}
/*!
* \internal
* \brief Callback function to terminate a memory cache
*
* \param data The sorcery memory cache
*/
static void sorcery_memory_cache_close(void *data)
{
struct sorcery_memory_cache *cache = data;
/* This can occur if a cache is created but never loaded */
if (!ast_strlen_zero(cache->name)) {
ao2_unlink(caches, cache);
}
if (cache->object_lifetime_maximum) {
/* If object lifetime support is enabled we need to explicitly drop all cached objects here
* and stop the scheduled task. Failure to do so could potentially keep the cache around for
* a prolonged period of time.
*/
ao2_wrlock(cache->objects);
ao2_callback(cache->objects, OBJ_UNLINK | OBJ_NOLOCK | OBJ_NODATA | OBJ_MULTIPLE,
NULL, NULL);
AST_SCHED_DEL_UNREF(sched, cache->expire_id, ao2_ref(cache, -1));
ao2_unlock(cache->objects);
}
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
ao2_ref(cache, -1);
}
#ifdef TEST_FRAMEWORK
/*! \brief Dummy sorcery object */
struct test_sorcery_object {
SORCERY_OBJECT(details);
};
/*!
* \internal
* \brief Allocator for test object
*
* \param id The identifier for the object
*
* \retval non-NULL success
* \retval NULL failure
*/
static void *test_sorcery_object_alloc(const char *id)
{
return ast_sorcery_generic_alloc(sizeof(struct test_sorcery_object), NULL);
}
/*!
* \internal
* \brief Allocator for test sorcery instance
*
* \retval non-NULL success
* \retval NULL failure
*/
static struct ast_sorcery *alloc_and_initialize_sorcery(void)
{
struct ast_sorcery *sorcery;
if (!(sorcery = ast_sorcery_open())) {
return NULL;
}
if ((ast_sorcery_apply_default(sorcery, "test", "memory", NULL) != AST_SORCERY_APPLY_SUCCESS) ||
ast_sorcery_internal_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) {
ast_sorcery_unref(sorcery);
return NULL;
}
return sorcery;
}
AST_TEST_DEFINE(open_with_valid_options)
{
int res = AST_TEST_PASS;
struct sorcery_memory_cache *cache;
switch (cmd) {
case TEST_INIT:
info->name = "open_with_valid_options";
info->category = "/res/res_sorcery_memory_cache/";
info->summary = "Attempt to create sorcery memory caches using valid options";
info->description = "This test performs the following:\n"
"\t* Creates a memory cache with default configuration\n"
"\t* Creates a memory cache with a maximum object count of 10 and verifies it\n"
"\t* Creates a memory cache with a maximum object lifetime of 60 and verifies it\n"
"\t* Creates a memory cache with a stale object lifetime of 90 and verifies it\n";
return AST_TEST_NOT_RUN;
case TEST_EXECUTE:
break;
}
cache = sorcery_memory_cache_open("");
if (!cache) {
ast_test_status_update(test, "Failed to create a sorcery memory cache using default configuration\n");
res = AST_TEST_FAIL;
} else {
sorcery_memory_cache_close(cache);
}
cache = sorcery_memory_cache_open("maximum_objects=10");
if (!cache) {
ast_test_status_update(test, "Failed to create a sorcery memory cache with a maximum object count of 10\n");
res = AST_TEST_FAIL;
} else {
if (cache->maximum_objects != 10) {
ast_test_status_update(test, "Created a sorcery memory cache with a maximum object count of 10 but it has '%u'\n",
cache->maximum_objects);
}
sorcery_memory_cache_close(cache);
}
cache = sorcery_memory_cache_open("object_lifetime_maximum=60");
if (!cache) {
ast_test_status_update(test, "Failed to create a sorcery memory cache with a maximum object lifetime of 60\n");
res = AST_TEST_FAIL;
} else {
if (cache->object_lifetime_maximum != 60) {
ast_test_status_update(test, "Created a sorcery memory cache with a maximum object lifetime of 60 but it has '%u'\n",
cache->object_lifetime_maximum);
}
sorcery_memory_cache_close(cache);
}
cache = sorcery_memory_cache_open("object_lifetime_stale=90");