2 * Asterisk -- An open source telephony toolkit.
4 * Copyright (C) 2015, Digium, Inc.
6 * Joshua Colp <jcolp@digium.com>
8 * See http://www.asterisk.org for more information about
9 * the Asterisk project. Please do not directly contact
10 * any of the maintainers of this project for assistance;
11 * the project provides a web site, mailing lists and IRC
12 * channels for your use.
14 * This program is free software, distributed under the terms of
15 * the GNU General Public License Version 2. See the LICENSE file
16 * at the top of the source tree.
22 * \brief Sorcery Memory Cache Object Wizard
24 * \author Joshua Colp <jcolp@digium.com>
28 <support_level>core</support_level>
33 ASTERISK_REGISTER_FILE()
35 #include "asterisk/module.h"
36 #include "asterisk/sorcery.h"
37 #include "asterisk/astobj2.h"
38 #include "asterisk/sched.h"
39 #include "asterisk/test.h"
40 #include "asterisk/heap.h"
42 /*! \brief Structure for storing a memory cache */
43 struct sorcery_memory_cache {
44 /*! \brief The name of the memory cache */
46 /*! \brief Objects in the cache */
47 struct ao2_container *objects;
48 /*! \brief The maximum number of objects permitted in the cache, 0 if no limit */
49 unsigned int maximum_objects;
50 /*! \brief The maximum time (in seconds) an object will stay in the cache, 0 if no limit */
51 unsigned int object_lifetime_maximum;
52 /*! \brief The amount of time (in seconds) before an object is marked as stale, 0 if disabled */
53 unsigned int object_lifetime_stale;
54 /*! \brief Whether objects are prefetched from normal storage at load time, 0 if disabled */
55 unsigned int prefetch;
56 /** \brief Whether all objects are expired when the object type is reloaded, 0 if disabled */
57 unsigned int expire_on_reload;
58 /*! \brief Heap of cached objects. Oldest object is at the top. */
59 struct ast_heap *object_heap;
60 /*! \brief Scheduler item for expiring oldest object. */
63 /*! \brief Variable used to indicate we should notify a test when we reach empty */
64 unsigned int cache_notify;
65 /*! \brief Mutex lock used for signaling when the cache has reached empty */
67 /*! \brief Condition used for signaling when the cache has reached empty */
69 /*! \brief Variable that is set when the cache has reached empty */
70 unsigned int cache_completed;
74 /*! \brief Structure for stored a cached object */
75 struct sorcery_memory_cached_object {
76 /*! \brief The cached object */
78 /*! \brief The time at which the object was created */
79 struct timeval created;
80 /*! \brief index required by heap */
82 /*! \brief scheduler id of stale update task */
83 int stale_update_sched_id;
86 static void *sorcery_memory_cache_open(const char *data);
87 static int sorcery_memory_cache_create(const struct ast_sorcery *sorcery, void *data, void *object);
88 static void sorcery_memory_cache_load(void *data, const struct ast_sorcery *sorcery, const char *type);
89 static void sorcery_memory_cache_reload(void *data, const struct ast_sorcery *sorcery, const char *type);
90 static void *sorcery_memory_cache_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type,
92 static int sorcery_memory_cache_delete(const struct ast_sorcery *sorcery, void *data, void *object);
93 static void sorcery_memory_cache_close(void *data);
95 static struct ast_sorcery_wizard memory_cache_object_wizard = {
96 .name = "memory_cache",
97 .open = sorcery_memory_cache_open,
98 .create = sorcery_memory_cache_create,
99 .update = sorcery_memory_cache_create,
100 .delete = sorcery_memory_cache_delete,
101 .load = sorcery_memory_cache_load,
102 .reload = sorcery_memory_cache_reload,
103 .retrieve_id = sorcery_memory_cache_retrieve_id,
104 .close = sorcery_memory_cache_close,
107 /*! \brief The bucket size for the container of caches */
108 #define CACHES_CONTAINER_BUCKET_SIZE 53
110 /*! \brief The default bucket size for the container of objects in the cache */
111 #define CACHE_CONTAINER_BUCKET_SIZE 53
113 /*! \brief Height of heap for cache object heap. Allows 31 initial objects */
114 #define CACHE_HEAP_INIT_HEIGHT 5
116 /*! \brief Container of created caches */
117 static struct ao2_container *caches;
119 /*! \brief Scheduler for cache management */
120 static struct ast_sched_context *sched;
122 #define STALE_UPDATE_THREAD_ID 0x5EED1E55
123 AST_THREADSTORAGE(stale_update_id_storage);
125 static int is_stale_update(void)
127 uint32_t *stale_update_thread_id;
129 stale_update_thread_id = ast_threadstorage_get(&stale_update_id_storage,
130 sizeof(*stale_update_thread_id));
131 if (!stale_update_thread_id) {
135 return *stale_update_thread_id == STALE_UPDATE_THREAD_ID;
138 static void start_stale_update(void)
140 uint32_t *stale_update_thread_id;
142 stale_update_thread_id = ast_threadstorage_get(&stale_update_id_storage,
143 sizeof(*stale_update_thread_id));
144 if (!stale_update_thread_id) {
145 ast_log(LOG_ERROR, "Could not set stale update ID for sorcery memory cache thread\n");
149 *stale_update_thread_id = STALE_UPDATE_THREAD_ID;
152 static void end_stale_update(void)
154 uint32_t *stale_update_thread_id;
156 stale_update_thread_id = ast_threadstorage_get(&stale_update_id_storage,
157 sizeof(*stale_update_thread_id));
158 if (!stale_update_thread_id) {
159 ast_log(LOG_ERROR, "Could not set stale update ID for sorcery memory cache thread\n");
163 *stale_update_thread_id = 0;
168 * \brief Hashing function for the container holding caches
170 * \param obj A sorcery memory cache or name of one
171 * \param flags Hashing flags
173 * \return The hash of the memory cache name
175 static int sorcery_memory_cache_hash(const void *obj, int flags)
177 const struct sorcery_memory_cache *cache = obj;
178 const char *name = obj;
181 switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
183 case OBJ_SEARCH_OBJECT:
187 hash = ast_str_hash(name);
189 case OBJ_SEARCH_PARTIAL_KEY:
190 /* Should never happen in hash callback. */
200 * \brief Comparison function for the container holding caches
202 * \param obj A sorcery memory cache
203 * \param arg A sorcery memory cache, or name of one
204 * \param flags Comparison flags
206 * \retval CMP_MATCH if the name is the same
207 * \retval 0 if the name does not match
209 static int sorcery_memory_cache_cmp(void *obj, void *arg, int flags)
211 const struct sorcery_memory_cache *left = obj;
212 const struct sorcery_memory_cache *right = arg;
213 const char *right_name = arg;
216 switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
218 case OBJ_SEARCH_OBJECT:
219 right_name = right->name;
222 cmp = strcmp(left->name, right_name);
224 case OBJ_SEARCH_PARTIAL_KEY:
225 cmp = strncmp(left->name, right_name, strlen(right_name));
228 return cmp ? 0 : CMP_MATCH;
233 * \brief Hashing function for the container holding cached objects
235 * \param obj A cached object or id of one
236 * \param flags Hashing flags
238 * \return The hash of the cached object id
240 static int sorcery_memory_cached_object_hash(const void *obj, int flags)
242 const struct sorcery_memory_cached_object *cached = obj;
243 const char *name = obj;
246 switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
248 case OBJ_SEARCH_OBJECT:
249 name = ast_sorcery_object_get_id(cached->object);
252 hash = ast_str_hash(name);
254 case OBJ_SEARCH_PARTIAL_KEY:
255 /* Should never happen in hash callback. */
265 * \brief Comparison function for the container holding cached objects
267 * \param obj A cached object
268 * \param arg A cached object, or id of one
269 * \param flags Comparison flags
271 * \retval CMP_MATCH if the id is the same
272 * \retval 0 if the id does not match
274 static int sorcery_memory_cached_object_cmp(void *obj, void *arg, int flags)
276 struct sorcery_memory_cached_object *left = obj;
277 struct sorcery_memory_cached_object *right = arg;
278 const char *right_name = arg;
281 switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
283 case OBJ_SEARCH_OBJECT:
284 right_name = ast_sorcery_object_get_id(right->object);
287 cmp = strcmp(ast_sorcery_object_get_id(left->object), right_name);
289 case OBJ_SEARCH_PARTIAL_KEY:
290 cmp = strncmp(ast_sorcery_object_get_id(left->object), right_name, strlen(right_name));
293 return cmp ? 0 : CMP_MATCH;
298 * \brief Destructor function for a sorcery memory cache
300 * \param obj A sorcery memory cache
302 static void sorcery_memory_cache_destructor(void *obj)
304 struct sorcery_memory_cache *cache = obj;
306 ast_free(cache->name);
307 ao2_cleanup(cache->objects);
308 if (cache->object_heap) {
309 ast_heap_destroy(cache->object_heap);
315 * \brief Destructor function for sorcery memory cached objects
317 * \param obj A sorcery memory cached object
319 static void sorcery_memory_cached_object_destructor(void *obj)
321 struct sorcery_memory_cached_object *cached = obj;
323 ao2_cleanup(cached->object);
326 static int schedule_cache_expiration(struct sorcery_memory_cache *cache);
330 * \brief Remove an object from the cache.
332 * This removes the item from both the hashtable and the heap.
334 * \pre cache->objects is write-locked
336 * \param cache The cache from which the object is being removed.
337 * \param id The sorcery object id of the object to remove.
338 * \param reschedule Reschedule cache expiration if this was the oldest object.
341 * \retval non-zero Failure
343 static int remove_from_cache(struct sorcery_memory_cache *cache, const char *id, int reschedule)
345 struct sorcery_memory_cached_object *hash_object;
346 struct sorcery_memory_cached_object *oldest_object;
347 struct sorcery_memory_cached_object *heap_object;
349 hash_object = ao2_find(cache->objects, id,
350 OBJ_SEARCH_KEY | OBJ_UNLINK | OBJ_NOLOCK);
354 oldest_object = ast_heap_peek(cache->object_heap, 1);
355 heap_object = ast_heap_remove(cache->object_heap, hash_object);
357 ast_assert(heap_object == hash_object);
359 ao2_ref(hash_object, -1);
361 if (reschedule && (oldest_object == heap_object)) {
362 schedule_cache_expiration(cache);
370 * \brief Scheduler callback invoked to expire old objects
372 * \param data The opaque callback data (in our case, the memory cache)
374 static int expire_objects_from_cache(const void *data)
376 struct sorcery_memory_cache *cache = (struct sorcery_memory_cache *)data;
377 struct sorcery_memory_cached_object *cached;
379 ao2_wrlock(cache->objects);
381 cache->expire_id = -1;
383 /* This is an optimization for objects which have been cached close to eachother */
384 while ((cached = ast_heap_peek(cache->object_heap, 1))) {
387 expiration = ast_tvdiff_ms(ast_tvadd(cached->created, ast_samp2tv(cache->object_lifetime_maximum, 1)), ast_tvnow());
389 /* If the current oldest object has not yet expired stop and reschedule for it */
390 if (expiration > 0) {
394 remove_from_cache(cache, ast_sorcery_object_get_id(cached->object), 0);
397 schedule_cache_expiration(cache);
399 ao2_unlock(cache->objects);
408 * \brief Schedule a callback for cached object expiration.
410 * \pre cache->objects is write-locked
412 * \param cache The cache that is having its callback scheduled.
417 static int schedule_cache_expiration(struct sorcery_memory_cache *cache)
419 struct sorcery_memory_cached_object *cached;
422 if (!cache->object_lifetime_maximum) {
426 if (cache->expire_id != -1) {
427 /* If we can't unschedule this expiration then it is currently attempting to run,
428 * so let it run - it just means that it'll be the one scheduling instead of us.
430 if (ast_sched_del(sched, cache->expire_id)) {
434 /* Since it successfully cancelled we need to drop the ref to the cache it had */
436 cache->expire_id = -1;
439 cached = ast_heap_peek(cache->object_heap, 1);
441 #ifdef TEST_FRAMEWORK
442 ast_mutex_lock(&cache->lock);
443 cache->cache_completed = 1;
444 ast_cond_signal(&cache->cond);
445 ast_mutex_unlock(&cache->lock);
450 expiration = MAX(ast_tvdiff_ms(ast_tvadd(cached->created, ast_samp2tv(cache->object_lifetime_maximum, 1)), ast_tvnow()),
453 cache->expire_id = ast_sched_add(sched, expiration, expire_objects_from_cache, ao2_bump(cache));
454 if (cache->expire_id < 0) {
464 * \brief Remove the oldest item from the cache.
466 * \pre cache->objects is write-locked
468 * \param cache The cache from which to remove the oldest object
471 * \retval non-zero Failure
473 static int remove_oldest_from_cache(struct sorcery_memory_cache *cache)
475 struct sorcery_memory_cached_object *heap_old_object;
476 struct sorcery_memory_cached_object *hash_old_object;
478 heap_old_object = ast_heap_pop(cache->object_heap);
479 if (!heap_old_object) {
482 hash_old_object = ao2_find(cache->objects, heap_old_object,
483 OBJ_SEARCH_OBJECT | OBJ_UNLINK | OBJ_NOLOCK);
485 ast_assert(heap_old_object == hash_old_object);
487 ao2_ref(hash_old_object, -1);
489 schedule_cache_expiration(cache);
496 * \brief Add a new object to the cache.
498 * \pre cache->objects is write-locked
500 * \param cache The cache in which to add the new object
501 * \param cached_object The object to add to the cache
504 * \retval non-zero Failure
506 static int add_to_cache(struct sorcery_memory_cache *cache,
507 struct sorcery_memory_cached_object *cached_object)
509 if (!ao2_link_flags(cache->objects, cached_object, OBJ_NOLOCK)) {
513 if (ast_heap_push(cache->object_heap, cached_object)) {
514 ao2_find(cache->objects, cached_object,
515 OBJ_SEARCH_OBJECT | OBJ_UNLINK | OBJ_NODATA | OBJ_NOLOCK);
519 if (cache->expire_id == -1) {
520 schedule_cache_expiration(cache);
528 * \brief Callback function to cache an object in a memory cache
530 * \param sorcery The sorcery instance
531 * \param data The sorcery memory cache
532 * \param object The object to cache
537 static int sorcery_memory_cache_create(const struct ast_sorcery *sorcery, void *data, void *object)
539 struct sorcery_memory_cache *cache = data;
540 struct sorcery_memory_cached_object *cached;
542 cached = ao2_alloc(sizeof(*cached), sorcery_memory_cached_object_destructor);
546 cached->object = ao2_bump(object);
547 cached->created = ast_tvnow();
548 cached->stale_update_sched_id = -1;
550 /* As there is no guarantee that this won't be called by multiple threads wanting to cache
551 * the same object we remove any old ones, which turns this into a create/update function
552 * in reality. As well since there's no guarantee that the object in the cache is the same
553 * one here we remove any old objects using the object identifier.
556 ao2_wrlock(cache->objects);
557 remove_from_cache(cache, ast_sorcery_object_get_id(object), 1);
558 if (cache->maximum_objects && ao2_container_count(cache->objects) >= cache->maximum_objects) {
559 if (remove_oldest_from_cache(cache)) {
560 ast_log(LOG_ERROR, "Unable to make room in cache for sorcery object '%s'.\n",
561 ast_sorcery_object_get_id(object));
563 ao2_unlock(cache->objects);
567 if (add_to_cache(cache, cached)) {
568 ast_log(LOG_ERROR, "Unable to add object '%s' to the cache\n",
569 ast_sorcery_object_get_id(object));
571 ao2_unlock(cache->objects);
574 ao2_unlock(cache->objects);
580 struct stale_update_task_data {
581 struct ast_sorcery *sorcery;
582 struct sorcery_memory_cache *cache;
586 static void stale_update_task_data_destructor(void *obj)
588 struct stale_update_task_data *task_data = obj;
590 ao2_cleanup(task_data->cache);
591 ao2_cleanup(task_data->object);
592 ast_sorcery_unref(task_data->sorcery);
595 static struct stale_update_task_data *stale_update_task_data_alloc(struct ast_sorcery *sorcery,
596 struct sorcery_memory_cache *cache, const char *type, void *object)
598 struct stale_update_task_data *task_data;
600 task_data = ao2_alloc_options(sizeof(*task_data), stale_update_task_data_destructor,
601 AO2_ALLOC_OPT_LOCK_NOLOCK);
606 task_data->sorcery = ao2_bump(sorcery);
607 task_data->cache = ao2_bump(cache);
608 task_data->object = ao2_bump(object);
613 static int stale_item_update(const void *data)
615 struct stale_update_task_data *task_data = (struct stale_update_task_data *) data;
618 start_stale_update();
620 object = ast_sorcery_retrieve_by_id(task_data->sorcery,
621 ast_sorcery_object_get_type(task_data->object),
622 ast_sorcery_object_get_id(task_data->object));
624 ast_debug(1, "Backend no longer has object type '%s' ID '%s'. Removing from cache\n",
625 ast_sorcery_object_get_type(task_data->object),
626 ast_sorcery_object_get_id(task_data->object));
627 sorcery_memory_cache_delete(task_data->sorcery, task_data->cache,
630 ast_debug(1, "Refreshing stale cache object type '%s' ID '%s'\n",
631 ast_sorcery_object_get_type(task_data->object),
632 ast_sorcery_object_get_id(task_data->object));
633 sorcery_memory_cache_create(task_data->sorcery, task_data->cache,
637 ao2_ref(task_data, -1);
645 * \brief Callback function to retrieve an object from a memory cache
647 * \param sorcery The sorcery instance
648 * \param data The sorcery memory cache
649 * \param type The type of the object to retrieve
650 * \param id The id of the object to retrieve
652 * \retval non-NULL success
653 * \retval NULL failure
655 static void *sorcery_memory_cache_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id)
657 struct sorcery_memory_cache *cache = data;
658 struct sorcery_memory_cached_object *cached;
661 if (is_stale_update()) {
665 cached = ao2_find(cache->objects, id, OBJ_SEARCH_KEY);
670 if (cache->object_lifetime_stale) {
671 struct timeval elapsed;
673 elapsed = ast_tvsub(ast_tvnow(), cached->created);
674 if (elapsed.tv_sec > cache->object_lifetime_stale) {
676 if (cached->stale_update_sched_id == -1) {
677 struct stale_update_task_data *task_data;
679 task_data = stale_update_task_data_alloc((struct ast_sorcery *)sorcery, cache,
680 type, cached->object);
682 ast_debug(1, "Cached sorcery object type '%s' ID '%s' is stale. Refreshing\n",
684 cached->stale_update_sched_id = ast_sched_add(sched, 1, stale_item_update, task_data);
686 ast_log(LOG_ERROR, "Unable to update stale cached object type '%s', ID '%s'.\n",
694 object = ao2_bump(cached->object);
702 * \brief Callback function to finish configuring the memory cache and to prefetch objects
704 * \param data The sorcery memory cache
705 * \param sorcery The sorcery instance
706 * \param type The type of object being loaded
708 static void sorcery_memory_cache_load(void *data, const struct ast_sorcery *sorcery, const char *type)
710 struct sorcery_memory_cache *cache = data;
712 /* If no name was explicitly specified generate one given the sorcery instance and object type */
713 if (ast_strlen_zero(cache->name)) {
714 ast_asprintf(&cache->name, "%s/%s", ast_sorcery_get_module(sorcery), type);
717 ao2_link(caches, cache);
718 ast_debug(1, "Memory cache '%s' associated with sorcery instance '%p' of module '%s' with object type '%s'\n",
719 cache->name, sorcery, ast_sorcery_get_module(sorcery), type);
724 * \brief Callback function to expire objects from the memory cache on reload (if configured)
726 * \param data The sorcery memory cache
727 * \param sorcery The sorcery instance
728 * \param type The type of object being reloaded
730 static void sorcery_memory_cache_reload(void *data, const struct ast_sorcery *sorcery, const char *type)
736 * \brief Function used to take an unsigned integer based configuration option and parse it
738 * \param value The string value of the configuration option
739 * \param result The unsigned integer to place the result in
744 static int configuration_parse_unsigned_integer(const char *value, unsigned int *result)
746 if (ast_strlen_zero(value) || !strncmp(value, "-", 1)) {
750 return sscanf(value, "%30u", result);
753 static int age_cmp(void *a, void *b)
755 return ast_tvcmp(((struct sorcery_memory_cached_object *) b)->created,
756 ((struct sorcery_memory_cached_object *) a)->created);
761 * \brief Callback function to create a new sorcery memory cache using provided configuration
763 * \param data A stringified configuration for the memory cache
765 * \retval non-NULL success
766 * \retval NULL failure
768 static void *sorcery_memory_cache_open(const char *data)
770 char *options = ast_strdup(data), *option;
771 RAII_VAR(struct sorcery_memory_cache *, cache, NULL, ao2_cleanup);
773 cache = ao2_alloc_options(sizeof(*cache), sorcery_memory_cache_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
778 cache->expire_id = -1;
780 /* If no configuration options have been provided this memory cache will operate in a default
783 while (!ast_strlen_zero(options) && (option = strsep(&options, ","))) {
784 char *name = strsep(&option, "="), *value = option;
786 if (!strcasecmp(name, "name")) {
787 if (ast_strlen_zero(value)) {
788 ast_log(LOG_ERROR, "A name must be specified for the memory cache\n");
791 ast_free(cache->name);
792 cache->name = ast_strdup(value);
793 } else if (!strcasecmp(name, "maximum_objects")) {
794 if (configuration_parse_unsigned_integer(value, &cache->maximum_objects) != 1) {
795 ast_log(LOG_ERROR, "Unsupported maximum objects value of '%s' used for memory cache\n",
799 } else if (!strcasecmp(name, "object_lifetime_maximum")) {
800 if (configuration_parse_unsigned_integer(value, &cache->object_lifetime_maximum) != 1) {
801 ast_log(LOG_ERROR, "Unsupported object maximum lifetime value of '%s' used for memory cache\n",
805 } else if (!strcasecmp(name, "object_lifetime_stale")) {
806 if (configuration_parse_unsigned_integer(value, &cache->object_lifetime_stale) != 1) {
807 ast_log(LOG_ERROR, "Unsupported object stale lifetime value of '%s' used for memory cache\n",
811 } else if (!strcasecmp(name, "prefetch")) {
812 cache->prefetch = ast_true(value);
813 } else if (!strcasecmp(name, "expire_on_reload")) {
814 cache->expire_on_reload = ast_true(value);
816 ast_log(LOG_ERROR, "Unsupported option '%s' used for memory cache\n", name);
821 cache->objects = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_RWLOCK,
822 cache->maximum_objects ? cache->maximum_objects : CACHE_CONTAINER_BUCKET_SIZE,
823 sorcery_memory_cached_object_hash, sorcery_memory_cached_object_cmp);
824 if (!cache->objects) {
825 ast_log(LOG_ERROR, "Could not create a container to hold cached objects for memory cache\n");
829 cache->object_heap = ast_heap_create(CACHE_HEAP_INIT_HEIGHT, age_cmp,
830 offsetof(struct sorcery_memory_cached_object, __heap_index));
831 if (!cache->object_heap) {
832 ast_log(LOG_ERROR, "Could not create heap to hold cached objects\n");
836 /* The memory cache is not linked to the caches container until the load callback is invoked.
837 * Linking occurs there so an intelligent cache name can be constructed using the module of
838 * the sorcery instance and the specific object type if no cache name was specified as part
839 * of the configuration.
842 /* This is done as RAII_VAR will drop the reference */
843 return ao2_bump(cache);
848 * \brief Callback function to delete an object from a memory cache
850 * \param sorcery The sorcery instance
851 * \param data The sorcery memory cache
852 * \param object The object to cache
857 static int sorcery_memory_cache_delete(const struct ast_sorcery *sorcery, void *data, void *object)
859 struct sorcery_memory_cache *cache = data;
862 ao2_wrlock(cache->objects);
863 res = remove_from_cache(cache, ast_sorcery_object_get_id(object), 1);
864 ao2_unlock(cache->objects);
867 ast_log(LOG_ERROR, "Unable to delete object '%s' from sorcery cache\n", ast_sorcery_object_get_id(object));
875 * \brief Callback function to terminate a memory cache
877 * \param data The sorcery memory cache
879 static void sorcery_memory_cache_close(void *data)
881 struct sorcery_memory_cache *cache = data;
883 /* This can occur if a cache is created but never loaded */
884 if (!ast_strlen_zero(cache->name)) {
885 ao2_unlink(caches, cache);
888 if (cache->object_lifetime_maximum) {
889 /* If object lifetime support is enabled we need to explicitly drop all cached objects here
890 * and stop the scheduled task. Failure to do so could potentially keep the cache around for
891 * a prolonged period of time.
893 ao2_wrlock(cache->objects);
894 ao2_callback(cache->objects, OBJ_UNLINK | OBJ_NOLOCK | OBJ_NODATA | OBJ_MULTIPLE,
896 AST_SCHED_DEL_UNREF(sched, cache->expire_id, ao2_ref(cache, -1));
897 ao2_unlock(cache->objects);
903 #ifdef TEST_FRAMEWORK
905 /*! \brief Dummy sorcery object */
906 struct test_sorcery_object {
907 SORCERY_OBJECT(details);
912 * \brief Allocator for test object
914 * \param id The identifier for the object
916 * \retval non-NULL success
917 * \retval NULL failure
919 static void *test_sorcery_object_alloc(const char *id)
921 return ast_sorcery_generic_alloc(sizeof(struct test_sorcery_object), NULL);
926 * \brief Allocator for test sorcery instance
928 * \retval non-NULL success
929 * \retval NULL failure
931 static struct ast_sorcery *alloc_and_initialize_sorcery(void)
933 struct ast_sorcery *sorcery;
935 if (!(sorcery = ast_sorcery_open())) {
939 if ((ast_sorcery_apply_default(sorcery, "test", "memory", NULL) != AST_SORCERY_APPLY_SUCCESS) ||
940 ast_sorcery_internal_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) {
941 ast_sorcery_unref(sorcery);
948 AST_TEST_DEFINE(open_with_valid_options)
950 int res = AST_TEST_PASS;
951 struct sorcery_memory_cache *cache;
955 info->name = "open_with_valid_options";
956 info->category = "/res/res_sorcery_memory_cache/";
957 info->summary = "Attempt to create sorcery memory caches using valid options";
958 info->description = "This test performs the following:\n"
959 "\t* Creates a memory cache with default configuration\n"
960 "\t* Creates a memory cache with a maximum object count of 10 and verifies it\n"
961 "\t* Creates a memory cache with a maximum object lifetime of 60 and verifies it\n"
962 "\t* Creates a memory cache with a stale object lifetime of 90 and verifies it\n";
963 return AST_TEST_NOT_RUN;
968 cache = sorcery_memory_cache_open("");
970 ast_test_status_update(test, "Failed to create a sorcery memory cache using default configuration\n");
973 sorcery_memory_cache_close(cache);
976 cache = sorcery_memory_cache_open("maximum_objects=10");
978 ast_test_status_update(test, "Failed to create a sorcery memory cache with a maximum object count of 10\n");
981 if (cache->maximum_objects != 10) {
982 ast_test_status_update(test, "Created a sorcery memory cache with a maximum object count of 10 but it has '%u'\n",
983 cache->maximum_objects);
985 sorcery_memory_cache_close(cache);
988 cache = sorcery_memory_cache_open("object_lifetime_maximum=60");
990 ast_test_status_update(test, "Failed to create a sorcery memory cache with a maximum object lifetime of 60\n");
993 if (cache->object_lifetime_maximum != 60) {
994 ast_test_status_update(test, "Created a sorcery memory cache with a maximum object lifetime of 60 but it has '%u'\n",
995 cache->object_lifetime_maximum);
997 sorcery_memory_cache_close(cache);
1000 cache = sorcery_memory_cache_open("object_lifetime_stale=90");
1002 ast_test_status_update(test, "Failed to create a sorcery memory cache with a stale object lifetime of 90\n");
1003 res = AST_TEST_FAIL;
1005 if (cache->object_lifetime_stale != 90) {
1006 ast_test_status_update(test, "Created a sorcery memory cache with a stale object lifetime of 90 but it has '%u'\n",
1007 cache->object_lifetime_stale);
1009 sorcery_memory_cache_close(cache);
1016 AST_TEST_DEFINE(open_with_invalid_options)
1018 int res = AST_TEST_PASS;
1019 struct sorcery_memory_cache *cache;
1023 info->name = "open_with_invalid_options";
1024 info->category = "/res/res_sorcery_memory_cache/";
1025 info->summary = "Attempt to create sorcery memory caches using invalid options";
1026 info->description = "This test attempts to perform the following:\n"
1027 "\t* Create a memory cache with an empty name\n"
1028 "\t* Create a memory cache with a maximum object count of -1\n"
1029 "\t* Create a memory cache with a maximum object count of toast\n"
1030 "\t* Create a memory cache with a maximum object lifetime of -1\n"
1031 "\t* Create a memory cache with a maximum object lifetime of toast\n"
1032 "\t* Create a memory cache with a stale object lifetime of -1\n"
1033 "\t* Create a memory cache with a stale object lifetime of toast\n";
1034 return AST_TEST_NOT_RUN;
1039 cache = sorcery_memory_cache_open("name=");
1041 ast_test_status_update(test, "Created a sorcery memory cache with an empty name\n");
1042 sorcery_memory_cache_close(cache);
1043 res = AST_TEST_FAIL;
1046 cache = sorcery_memory_cache_open("maximum_objects=-1");
1048 ast_test_status_update(test, "Created a sorcery memory cache with a maximum object count of -1\n");
1049 sorcery_memory_cache_close(cache);
1050 res = AST_TEST_FAIL;
1053 cache = sorcery_memory_cache_open("maximum_objects=toast");
1055 ast_test_status_update(test, "Created a sorcery memory cache with a maximum object count of toast\n");
1056 sorcery_memory_cache_close(cache);
1057 res = AST_TEST_FAIL;
1060 cache = sorcery_memory_cache_open("object_lifetime_maximum=-1");
1062 ast_test_status_update(test, "Created a sorcery memory cache with an object lifetime maximum of -1\n");
1063 sorcery_memory_cache_close(cache);
1064 res = AST_TEST_FAIL;
1067 cache = sorcery_memory_cache_open("object_lifetime_maximum=toast");
1069 ast_test_status_update(test, "Created a sorcery memory cache with an object lifetime maximum of toast\n");
1070 sorcery_memory_cache_close(cache);
1071 res = AST_TEST_FAIL;
1074 cache = sorcery_memory_cache_open("object_lifetime_stale=-1");
1076 ast_test_status_update(test, "Created a sorcery memory cache with a stale object lifetime of -1\n");
1077 sorcery_memory_cache_close(cache);
1078 res = AST_TEST_FAIL;
1081 cache = sorcery_memory_cache_open("object_lifetime_stale=toast");
1083 ast_test_status_update(test, "Created a sorcery memory cache with a stale object lifetime of toast\n");
1084 sorcery_memory_cache_close(cache);
1085 res = AST_TEST_FAIL;
1088 cache = sorcery_memory_cache_open("tacos");
1090 ast_test_status_update(test, "Created a sorcery memory cache with an invalid configuration option 'tacos'\n");
1091 sorcery_memory_cache_close(cache);
1092 res = AST_TEST_FAIL;
1098 AST_TEST_DEFINE(create_and_retrieve)
1100 int res = AST_TEST_FAIL;
1101 struct ast_sorcery *sorcery = NULL;
1102 struct sorcery_memory_cache *cache = NULL;
1103 RAII_VAR(void *, object, NULL, ao2_cleanup);
1104 RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
1108 info->name = "create";
1109 info->category = "/res/res_sorcery_memory_cache/";
1110 info->summary = "Attempt to create an object in the cache";
1111 info->description = "This test performs the following:\n"
1112 "\t* Creates a memory cache with default options\n"
1113 "\t* Creates a sorcery instance with a test object\n"
1114 "\t* Creates a test object with an id of test\n"
1115 "\t* Pushes the test object into the memory cache\n"
1116 "\t* Confirms that the test object is in the cache\n";
1117 return AST_TEST_NOT_RUN;
1122 cache = sorcery_memory_cache_open("");
1124 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
1128 if (ao2_container_count(cache->objects)) {
1129 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
1133 sorcery = alloc_and_initialize_sorcery();
1135 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
1139 object = ast_sorcery_alloc(sorcery, "test", "test");
1141 ast_test_status_update(test, "Failed to allocate a test object\n");
1145 sorcery_memory_cache_create(sorcery, cache, object);
1147 if (!ao2_container_count(cache->objects)) {
1148 ast_test_status_update(test, "Added test object to memory cache but cache remains empty\n");
1152 cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
1153 if (!cached_object) {
1154 ast_test_status_update(test, "Object placed into memory cache could not be retrieved\n");
1158 if (cached_object != object) {
1159 ast_test_status_update(test, "Object retrieved from memory cached is not the one we cached\n");
1163 res = AST_TEST_PASS;
1167 sorcery_memory_cache_close(cache);
1170 ast_sorcery_unref(sorcery);
1176 AST_TEST_DEFINE(update)
1178 int res = AST_TEST_FAIL;
1179 struct ast_sorcery *sorcery = NULL;
1180 struct sorcery_memory_cache *cache = NULL;
1181 RAII_VAR(void *, original_object, NULL, ao2_cleanup);
1182 RAII_VAR(void *, updated_object, NULL, ao2_cleanup);
1183 RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
1187 info->name = "create";
1188 info->category = "/res/res_sorcery_memory_cache/";
1189 info->summary = "Attempt to create and then update an object in the cache";
1190 info->description = "This test performs the following:\n"
1191 "\t* Creates a memory cache with default options\n"
1192 "\t* Creates a sorcery instance with a test object\n"
1193 "\t* Creates a test object with an id of test\n"
1194 "\t* Pushes the test object into the memory cache\n"
1195 "\t* Confirms that the test object is in the cache\n"
1196 "\t* Creates a new test object with the same id of test\n"
1197 "\t* Pushes the new test object into the memory cache\n"
1198 "\t* Confirms that the new test object has replaced the old one\n";
1199 return AST_TEST_NOT_RUN;
1204 cache = sorcery_memory_cache_open("");
1206 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
1210 if (ao2_container_count(cache->objects)) {
1211 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
1215 sorcery = alloc_and_initialize_sorcery();
1217 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
1221 original_object = ast_sorcery_alloc(sorcery, "test", "test");
1222 if (!original_object) {
1223 ast_test_status_update(test, "Failed to allocate a test object\n");
1227 sorcery_memory_cache_create(sorcery, cache, original_object);
1229 updated_object = ast_sorcery_alloc(sorcery, "test", "test");
1230 if (!updated_object) {
1231 ast_test_status_update(test, "Failed to allocate an updated test object\n");
1235 sorcery_memory_cache_create(sorcery, cache, updated_object);
1237 if (ao2_container_count(cache->objects) != 1) {
1238 ast_test_status_update(test, "Added updated test object to memory cache but cache now contains %d objects instead of 1\n",
1239 ao2_container_count(cache->objects));
1243 cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
1244 if (!cached_object) {
1245 ast_test_status_update(test, "Updated object placed into memory cache could not be retrieved\n");
1249 if (cached_object == original_object) {
1250 ast_test_status_update(test, "Updated object placed into memory cache but old one is being retrieved\n");
1252 } else if (cached_object != updated_object) {
1253 ast_test_status_update(test, "Updated object placed into memory cache but different one is being retrieved\n");
1257 res = AST_TEST_PASS;
1261 sorcery_memory_cache_close(cache);
1264 ast_sorcery_unref(sorcery);
1270 AST_TEST_DEFINE(delete)
1272 int res = AST_TEST_FAIL;
1273 struct ast_sorcery *sorcery = NULL;
1274 struct sorcery_memory_cache *cache = NULL;
1275 RAII_VAR(void *, object, NULL, ao2_cleanup);
1276 RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
1280 info->name = "delete";
1281 info->category = "/res/res_sorcery_memory_cache/";
1282 info->summary = "Attempt to create and then delete an object in the cache";
1283 info->description = "This test performs the following:\n"
1284 "\t* Creates a memory cache with default options\n"
1285 "\t* Creates a sorcery instance with a test object\n"
1286 "\t* Creates a test object with an id of test\n"
1287 "\t* Pushes the test object into the memory cache\n"
1288 "\t* Confirms that the test object is in the cache\n"
1289 "\t* Deletes the test object from the cache\n"
1290 "\t* Confirms that the test object is no longer in the cache\n";
1291 return AST_TEST_NOT_RUN;
1296 cache = sorcery_memory_cache_open("");
1298 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
1302 if (ao2_container_count(cache->objects)) {
1303 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
1307 sorcery = alloc_and_initialize_sorcery();
1309 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
1313 object = ast_sorcery_alloc(sorcery, "test", "test");
1315 ast_test_status_update(test, "Failed to allocate a test object\n");
1319 sorcery_memory_cache_create(sorcery, cache, object);
1321 if (!ao2_container_count(cache->objects)) {
1322 ast_test_status_update(test, "Added test object to memory cache but cache contains no objects\n");
1326 cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
1327 if (!cached_object) {
1328 ast_test_status_update(test, "Test object placed into memory cache could not be retrieved\n");
1332 ao2_ref(cached_object, -1);
1333 cached_object = NULL;
1335 sorcery_memory_cache_delete(sorcery, cache, object);
1337 cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
1338 if (cached_object) {
1339 ast_test_status_update(test, "Test object deleted from memory cache can still be retrieved\n");
1343 res = AST_TEST_PASS;
1347 sorcery_memory_cache_close(cache);
1350 ast_sorcery_unref(sorcery);
1356 static int check_cache_content(struct ast_test *test, struct ast_sorcery *sorcery, struct sorcery_memory_cache *cache,
1357 const char **in_cache, size_t num_in_cache, const char **not_in_cache, size_t num_not_in_cache)
1361 RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
1363 for (i = 0; i < num_in_cache; ++i) {
1364 cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", in_cache[i]);
1365 if (!cached_object) {
1366 ast_test_status_update(test, "Failed to retrieve '%s' object from the cache\n",
1370 ao2_ref(cached_object, -1);
1373 for (i = 0; i < num_not_in_cache; ++i) {
1374 cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", not_in_cache[i]);
1375 if (cached_object) {
1376 ast_test_status_update(test, "Retrieved '%s' object from the cache unexpectedly\n",
1378 ao2_ref(cached_object, -1);
1386 AST_TEST_DEFINE(maximum_objects)
1388 int res = AST_TEST_FAIL;
1389 struct ast_sorcery *sorcery = NULL;
1390 struct sorcery_memory_cache *cache = NULL;
1391 RAII_VAR(void *, alice, NULL, ao2_cleanup);
1392 RAII_VAR(void *, bob, NULL, ao2_cleanup);
1393 RAII_VAR(void *, charlie, NULL, ao2_cleanup);
1394 RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
1395 const char *in_cache[2];
1396 const char *not_in_cache[2];
1400 info->name = "maximum_objects";
1401 info->category = "/res/res_sorcery_memory_cache/";
1402 info->summary = "Ensure that the 'maximum_objects' option works as expected";
1403 info->description = "This test performs the following:\n"
1404 "\t* Creates a memory cache with maximum_objects=2\n"
1405 "\t* Creates a sorcery instance\n"
1406 "\t* Creates a three test objects: alice, bob, charlie, and david\n"
1407 "\t* Pushes alice and bob into the memory cache\n"
1408 "\t* Confirms that alice and bob are in the memory cache\n"
1409 "\t* Pushes charlie into the memory cache\n"
1410 "\t* Confirms that bob and charlie are in the memory cache\n"
1411 "\t* Deletes charlie from the memory cache\n"
1412 "\t* Confirms that only bob is in the memory cache\n"
1413 "\t* Pushes alice into the memory cache\n"
1414 "\t* Confirms that bob and alice are in the memory cache\n";
1415 return AST_TEST_NOT_RUN;
1420 cache = sorcery_memory_cache_open("maximum_objects=2");
1422 ast_test_status_update(test, "Failed to create a sorcery memory cache with maximum_objects=2\n");
1426 if (ao2_container_count(cache->objects)) {
1427 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
1431 sorcery = alloc_and_initialize_sorcery();
1433 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
1437 alice = ast_sorcery_alloc(sorcery, "test", "alice");
1438 bob = ast_sorcery_alloc(sorcery, "test", "bob");
1439 charlie = ast_sorcery_alloc(sorcery, "test", "charlie");
1441 if (!alice || !bob || !charlie) {
1442 ast_test_status_update(test, "Failed to allocate sorcery object(s)\n");
1446 sorcery_memory_cache_create(sorcery, cache, alice);
1447 in_cache[0] = "alice";
1449 not_in_cache[0] = "bob";
1450 not_in_cache[1] = "charlie";
1451 if (check_cache_content(test, sorcery, cache, in_cache, 1, not_in_cache, 2)) {
1455 /* Delays are added to ensure that we are not adding cache entries within the
1460 sorcery_memory_cache_create(sorcery, cache, bob);
1461 in_cache[0] = "alice";
1462 in_cache[1] = "bob";
1463 not_in_cache[0] = "charlie";
1464 not_in_cache[1] = NULL;
1465 if (check_cache_content(test, sorcery, cache, in_cache, 2, not_in_cache, 1)) {
1471 sorcery_memory_cache_create(sorcery, cache, charlie);
1472 in_cache[0] = "bob";
1473 in_cache[1] = "charlie";
1474 not_in_cache[0] = "alice";
1475 not_in_cache[1] = NULL;
1476 if (check_cache_content(test, sorcery, cache, in_cache, 2, not_in_cache, 1)) {
1481 sorcery_memory_cache_delete(sorcery, cache, charlie);
1482 in_cache[0] = "bob";
1484 not_in_cache[0] = "alice";
1485 not_in_cache[1] = "charlie";
1486 if (check_cache_content(test, sorcery, cache, in_cache, 1, not_in_cache, 2)) {
1491 sorcery_memory_cache_create(sorcery, cache, alice);
1492 in_cache[0] = "bob";
1493 in_cache[1] = "alice";
1494 not_in_cache[0] = "charlie";
1495 not_in_cache[1] = NULL;
1496 if (check_cache_content(test, sorcery, cache, in_cache, 2, not_in_cache, 1)) {
1500 res = AST_TEST_PASS;
1504 sorcery_memory_cache_close(cache);
1507 ast_sorcery_unref(sorcery);
1513 AST_TEST_DEFINE(expiration)
1515 int res = AST_TEST_FAIL;
1516 struct ast_sorcery *sorcery = NULL;
1517 struct sorcery_memory_cache *cache = NULL;
1522 info->name = "expiration";
1523 info->category = "/res/res_sorcery_memory_cache/";
1524 info->summary = "Add objects to a cache configured with maximum lifetime, confirm they are removed";
1525 info->description = "This test performs the following:\n"
1526 "\t* Creates a memory cache with a maximum object lifetime of 5 seconds\n"
1527 "\t* Pushes 10 objects into the memory cache\n"
1528 "\t* Waits (up to) 10 seconds for expiration to occur\n"
1529 "\t* Confirms that the objects have been removed from the cache\n";
1530 return AST_TEST_NOT_RUN;
1535 cache = sorcery_memory_cache_open("object_lifetime_maximum=5");
1537 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
1541 sorcery = alloc_and_initialize_sorcery();
1543 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
1547 cache->cache_notify = 1;
1548 ast_mutex_init(&cache->lock);
1549 ast_cond_init(&cache->cond, NULL);
1551 for (i = 0; i < 5; ++i) {
1552 char uuid[AST_UUID_STR_LEN];
1555 object = ast_sorcery_alloc(sorcery, "test", ast_uuid_generate_str(uuid, sizeof(uuid)));
1557 ast_test_status_update(test, "Failed to allocate test object for expiration\n");
1561 sorcery_memory_cache_create(sorcery, cache, object);
1563 ao2_ref(object, -1);
1566 ast_mutex_lock(&cache->lock);
1567 while (!cache->cache_completed) {
1568 struct timeval start = ast_tvnow();
1569 struct timespec end = {
1570 .tv_sec = start.tv_sec + 10,
1571 .tv_nsec = start.tv_usec * 1000,
1574 if (ast_cond_timedwait(&cache->cond, &cache->lock, &end) == ETIMEDOUT) {
1578 ast_mutex_unlock(&cache->lock);
1580 if (ao2_container_count(cache->objects)) {
1581 ast_test_status_update(test, "Objects placed into the memory cache did not expire and get removed\n");
1585 res = AST_TEST_PASS;
1589 if (cache->cache_notify) {
1590 ast_cond_destroy(&cache->cond);
1591 ast_mutex_destroy(&cache->lock);
1593 sorcery_memory_cache_close(cache);
1596 ast_sorcery_unref(sorcery);
1603 * \brief Backend data that the mock sorcery wizard uses to create objects
1605 static struct backend_data {
1606 /*! An arbitrary data field */
1608 /*! Another arbitrary data field */
1610 /*! Indicates whether the backend has data */
1612 } *real_backend_data;
1615 * \brief Sorcery object created based on backend data
1618 SORCERY_OBJECT(details);
1619 /*! Mirrors the backend data's salt field */
1621 /*! Mirrors the backend data's pepper field */
1626 * \brief Allocation callback for test_data sorcery object
1628 static void *test_data_alloc(const char *id) {
1629 return ast_sorcery_generic_alloc(sizeof(struct test_data), NULL);
1633 * \brief Callback for retrieving sorcery object by ID
1635 * The mock wizard uses the \ref real_backend_data in order to construct
1636 * objects. If the backend data is "nonexisent" then no object is returned.
1637 * Otherwise, an object is created that has the backend data's salt and
1638 * pepper values copied.
1640 * \param sorcery The sorcery instance
1641 * \param data Unused
1642 * \param type The object type. Will always be "test".
1643 * \param id The object id. Will always be "test".
1645 * \retval NULL Backend data does not exist
1646 * \retval non-NULL An object representing the backend data
1648 static void *mock_retrieve_id(const struct ast_sorcery *sorcery, void *data,
1649 const char *type, const char *id)
1651 struct test_data *b_data;
1653 if (!real_backend_data->exists) {
1657 b_data = ast_sorcery_alloc(sorcery, type, id);
1662 b_data->salt = real_backend_data->salt;
1663 b_data->pepper = real_backend_data->pepper;
1668 * \brief A mock sorcery wizard used for the stale test
1670 static struct ast_sorcery_wizard mock_wizard = {
1672 .retrieve_id = mock_retrieve_id,
1676 * \brief Wait for the cache to be updated after a stale object is retrieved.
1678 * Since the cache does not know what type of objects it is dealing with, and
1679 * since we do not have the internals of the cache, the only way to make this
1680 * determination is to continuously retrieve an object from the cache until
1681 * we retrieve a different object than we had previously retrieved.
1683 * \param sorcery The sorcery instance
1684 * \param previous_object The object we had previously retrieved from the cache
1685 * \param[out] new_object The new object we retrieve from the cache
1687 * \retval 0 Successfully retrieved a new object from the cache
1688 * \retval non-zero Failed to retrieve a new object from the cache
1690 static int wait_for_cache_update(const struct ast_sorcery *sorcery,
1691 void *previous_object, struct test_data **new_object)
1693 struct timeval start = ast_tvnow();
1695 while (ast_remaining_ms(start, 5000) > 0) {
1698 object = ast_sorcery_retrieve_by_id(sorcery, "test", "test");
1699 if (object != previous_object) {
1700 *new_object = object;
1703 ao2_cleanup(object);
1709 AST_TEST_DEFINE(stale)
1711 int res = AST_TEST_FAIL;
1712 struct ast_sorcery *sorcery = NULL;
1713 struct test_data *backend_object;
1714 struct backend_data iterations[] = {
1715 { .salt = 1, .pepper = 2, .exists = 1 },
1716 { .salt = 568729, .pepper = -234123, .exists = 1 },
1717 { .salt = 0, .pepper = 0, .exists = 0 },
1719 struct backend_data initial = {
1728 info->name = "stale";
1729 info->category = "/res/res_sorcery_memory_cache/";
1730 info->summary = "Ensure that stale objects are replaced with updated objects";
1731 info->description = "This test performs the following:\n"
1732 "\t* Create a sorcery instance with two wizards"
1733 "\t\t* The first is a memory cache that marks items stale after 3 seconds\n"
1734 "\t\t* The second is a mock of a back-end\n"
1735 "\t* Pre-populates the cache by retrieving some initial data from the backend.\n"
1736 "\t* Performs iterations of the following:\n"
1737 "\t\t* Update backend data with new values\n"
1738 "\t\t* Retrieve item from the cache\n"
1739 "\t\t* Ensure the retrieved item does not have the new backend values\n"
1740 "\t\t* Wait for cached object to become stale\n"
1741 "\t\t* Retrieve the stale cached object\n"
1742 "\t\t* Ensure that the stale object retrieved is the same as the fresh one from earlier\n"
1743 "\t\t* Wait for the cache to update with new data\n"
1744 "\t\t* Ensure that new data in the cache matches backend data\n";
1745 return AST_TEST_NOT_RUN;
1750 ast_sorcery_wizard_register(&mock_wizard);
1752 sorcery = ast_sorcery_open();
1754 ast_test_status_update(test, "Failed to create sorcery instance\n");
1758 ast_sorcery_apply_wizard_mapping(sorcery, "test", "memory_cache",
1759 "object_lifetime_stale=3", 1);
1760 ast_sorcery_apply_wizard_mapping(sorcery, "test", "mock", NULL, 0);
1761 ast_sorcery_internal_object_register(sorcery, "test", test_data_alloc, NULL, NULL);
1763 /* Prepopulate the cache */
1764 real_backend_data = &initial;
1766 backend_object = ast_sorcery_retrieve_by_id(sorcery, "test", "test");
1767 if (!backend_object) {
1768 ast_test_status_update(test, "Unable to retrieve backend data and populate the cache\n");
1771 ao2_ref(backend_object, -1);
1773 for (i = 0; i < ARRAY_LEN(iterations); ++i) {
1774 RAII_VAR(struct test_data *, cache_fresh, NULL, ao2_cleanup);
1775 RAII_VAR(struct test_data *, cache_stale, NULL, ao2_cleanup);
1776 RAII_VAR(struct test_data *, cache_new, NULL, ao2_cleanup);
1778 real_backend_data = &iterations[i];
1780 ast_test_status_update(test, "Begininning iteration %d\n", i);
1782 cache_fresh = ast_sorcery_retrieve_by_id(sorcery, "test", "test");
1784 ast_test_status_update(test, "Unable to retrieve fresh cached object\n");
1788 if (cache_fresh->salt == iterations[i].salt || cache_fresh->pepper == iterations[i].pepper) {
1789 ast_test_status_update(test, "Fresh cached object has unexpected values. Did we hit the backend?\n");
1795 cache_stale = ast_sorcery_retrieve_by_id(sorcery, "test", "test");
1797 ast_test_status_update(test, "Unable to retrieve stale cached object\n");
1801 if (cache_stale != cache_fresh) {
1802 ast_test_status_update(test, "Stale cache hit retrieved different object than fresh cache hit\n");
1806 if (wait_for_cache_update(sorcery, cache_stale, &cache_new)) {
1807 ast_test_status_update(test, "Cache was not updated\n");
1811 if (iterations[i].exists) {
1813 ast_test_status_update(test, "Failed to retrieve item from cache when there should be one present\n");
1815 } else if (cache_new->salt != iterations[i].salt ||
1816 cache_new->pepper != iterations[i].pepper) {
1817 ast_test_status_update(test, "New cached item has unexpected values\n");
1820 } else if (cache_new) {
1821 ast_test_status_update(test, "Retrieved a cached item when there should not have been one present\n");
1826 res = AST_TEST_PASS;
1830 ast_sorcery_unref(sorcery);
1832 ast_sorcery_wizard_unregister(&mock_wizard);
1838 static int unload_module(void)
1841 ast_sched_context_destroy(sched);
1845 ao2_cleanup(caches);
1847 ast_sorcery_wizard_unregister(&memory_cache_object_wizard);
1849 AST_TEST_UNREGISTER(open_with_valid_options);
1850 AST_TEST_UNREGISTER(open_with_invalid_options);
1851 AST_TEST_UNREGISTER(create_and_retrieve);
1852 AST_TEST_UNREGISTER(update);
1853 AST_TEST_UNREGISTER(delete);
1854 AST_TEST_UNREGISTER(maximum_objects);
1855 AST_TEST_UNREGISTER(expiration);
1856 AST_TEST_UNREGISTER(stale);
1861 static int load_module(void)
1863 sched = ast_sched_context_create();
1865 ast_log(LOG_ERROR, "Failed to create scheduler for cache management\n");
1867 return AST_MODULE_LOAD_DECLINE;
1870 if (ast_sched_start_thread(sched)) {
1871 ast_log(LOG_ERROR, "Failed to create scheduler thread for cache management\n");
1873 return AST_MODULE_LOAD_DECLINE;
1876 caches = ao2_container_alloc(CACHES_CONTAINER_BUCKET_SIZE, sorcery_memory_cache_hash,
1877 sorcery_memory_cache_cmp);
1879 ast_log(LOG_ERROR, "Failed to create container for configured caches\n");
1881 return AST_MODULE_LOAD_DECLINE;
1884 if (ast_sorcery_wizard_register(&memory_cache_object_wizard)) {
1886 return AST_MODULE_LOAD_DECLINE;
1889 AST_TEST_REGISTER(open_with_valid_options);
1890 AST_TEST_REGISTER(open_with_invalid_options);
1891 AST_TEST_REGISTER(create_and_retrieve);
1892 AST_TEST_REGISTER(update);
1893 AST_TEST_REGISTER(delete);
1894 AST_TEST_REGISTER(maximum_objects);
1895 AST_TEST_REGISTER(expiration);
1896 AST_TEST_REGISTER(stale);
1898 return AST_MODULE_LOAD_SUCCESS;
1901 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Sorcery Memory Cache Object Wizard",
1902 .support_level = AST_MODULE_SUPPORT_CORE,
1903 .load = load_module,
1904 .unload = unload_module,
1905 .load_pri = AST_MODPRI_REALTIME_DRIVER,