res_sorcery_memory_cache: Add support for refreshing stale objects.
[asterisk/asterisk.git] / res / res_sorcery_memory_cache.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 2015, Digium, Inc.
5  *
6  * Joshua Colp <jcolp@digium.com>
7  *
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.
13  *
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.
17  */
18
19 /*!
20  * \file
21  *
22  * \brief Sorcery Memory Cache Object Wizard
23  *
24  * \author Joshua Colp <jcolp@digium.com>
25  */
26
27 /*** MODULEINFO
28         <support_level>core</support_level>
29  ***/
30
31 #include "asterisk.h"
32
33 ASTERISK_REGISTER_FILE()
34
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"
41
42 /*! \brief Structure for storing a memory cache */
43 struct sorcery_memory_cache {
44         /*! \brief The name of the memory cache */
45         char *name;
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. */
61         int expire_id;
62 #ifdef TEST_FRAMEWORK
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 */
66         ast_mutex_t lock;
67         /*! \brief Condition used for signaling when the cache has reached empty */
68         ast_cond_t cond;
69         /*! \brief Variable that is set when the cache has reached empty */
70         unsigned int cache_completed;
71 #endif
72 };
73
74 /*! \brief Structure for stored a cached object */
75 struct sorcery_memory_cached_object {
76         /*! \brief The cached object */
77         void *object;
78         /*! \brief The time at which the object was created */
79         struct timeval created;
80         /*! \brief index required by heap */
81         ssize_t __heap_index;
82         /*! \brief scheduler id of stale update task */
83         int stale_update_sched_id;
84 };
85
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,
91         const char *id);
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);
94
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,
105 };
106
107 /*! \brief The bucket size for the container of caches */
108 #define CACHES_CONTAINER_BUCKET_SIZE 53
109
110 /*! \brief The default bucket size for the container of objects in the cache */
111 #define CACHE_CONTAINER_BUCKET_SIZE 53
112
113 /*! \brief Height of heap for cache object heap. Allows 31 initial objects */
114 #define CACHE_HEAP_INIT_HEIGHT 5
115
116 /*! \brief Container of created caches */
117 static struct ao2_container *caches;
118
119 /*! \brief Scheduler for cache management */
120 static struct ast_sched_context *sched;
121
122 #define STALE_UPDATE_THREAD_ID 0x5EED1E55
123 AST_THREADSTORAGE(stale_update_id_storage);
124
125 static int is_stale_update(void)
126 {
127         uint32_t *stale_update_thread_id;
128
129         stale_update_thread_id = ast_threadstorage_get(&stale_update_id_storage,
130                 sizeof(*stale_update_thread_id));
131         if (!stale_update_thread_id) {
132                 return 0;
133         }
134
135         return *stale_update_thread_id == STALE_UPDATE_THREAD_ID;
136 }
137
138 static void start_stale_update(void)
139 {
140         uint32_t *stale_update_thread_id;
141
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");
146                 return;
147         }
148
149         *stale_update_thread_id = STALE_UPDATE_THREAD_ID;
150 }
151
152 static void end_stale_update(void)
153 {
154         uint32_t *stale_update_thread_id;
155
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");
160                 return;
161         }
162
163         *stale_update_thread_id = 0;
164 }
165
166 /*!
167  * \internal
168  * \brief Hashing function for the container holding caches
169  *
170  * \param obj A sorcery memory cache or name of one
171  * \param flags Hashing flags
172  *
173  * \return The hash of the memory cache name
174  */
175 static int sorcery_memory_cache_hash(const void *obj, int flags)
176 {
177         const struct sorcery_memory_cache *cache = obj;
178         const char *name = obj;
179         int hash;
180
181         switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
182         default:
183         case OBJ_SEARCH_OBJECT:
184                 name = cache->name;
185                 /* Fall through */
186         case OBJ_SEARCH_KEY:
187                 hash = ast_str_hash(name);
188                 break;
189         case OBJ_SEARCH_PARTIAL_KEY:
190                 /* Should never happen in hash callback. */
191                 ast_assert(0);
192                 hash = 0;
193                 break;
194         }
195         return hash;
196 }
197
198 /*!
199  * \internal
200  * \brief Comparison function for the container holding caches
201  *
202  * \param obj A sorcery memory cache
203  * \param arg A sorcery memory cache, or name of one
204  * \param flags Comparison flags
205  *
206  * \retval CMP_MATCH if the name is the same
207  * \retval 0 if the name does not match
208  */
209 static int sorcery_memory_cache_cmp(void *obj, void *arg, int flags)
210 {
211         const struct sorcery_memory_cache *left = obj;
212         const struct sorcery_memory_cache *right = arg;
213         const char *right_name = arg;
214         int cmp;
215
216         switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
217         default:
218         case OBJ_SEARCH_OBJECT:
219                 right_name = right->name;
220                 /* Fall through */
221         case OBJ_SEARCH_KEY:
222                 cmp = strcmp(left->name, right_name);
223                 break;
224         case OBJ_SEARCH_PARTIAL_KEY:
225                 cmp = strncmp(left->name, right_name, strlen(right_name));
226                 break;
227         }
228         return cmp ? 0 : CMP_MATCH;
229 }
230
231 /*!
232  * \internal
233  * \brief Hashing function for the container holding cached objects
234  *
235  * \param obj A cached object or id of one
236  * \param flags Hashing flags
237  *
238  * \return The hash of the cached object id
239  */
240 static int sorcery_memory_cached_object_hash(const void *obj, int flags)
241 {
242         const struct sorcery_memory_cached_object *cached = obj;
243         const char *name = obj;
244         int hash;
245
246         switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
247         default:
248         case OBJ_SEARCH_OBJECT:
249                 name = ast_sorcery_object_get_id(cached->object);
250                 /* Fall through */
251         case OBJ_SEARCH_KEY:
252                 hash = ast_str_hash(name);
253                 break;
254         case OBJ_SEARCH_PARTIAL_KEY:
255                 /* Should never happen in hash callback. */
256                 ast_assert(0);
257                 hash = 0;
258                 break;
259         }
260         return hash;
261 }
262
263 /*!
264  * \internal
265  * \brief Comparison function for the container holding cached objects
266  *
267  * \param obj A cached object
268  * \param arg A cached object, or id of one
269  * \param flags Comparison flags
270  *
271  * \retval CMP_MATCH if the id is the same
272  * \retval 0 if the id does not match
273  */
274 static int sorcery_memory_cached_object_cmp(void *obj, void *arg, int flags)
275 {
276         struct sorcery_memory_cached_object *left = obj;
277         struct sorcery_memory_cached_object *right = arg;
278         const char *right_name = arg;
279         int cmp;
280
281         switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
282         default:
283         case OBJ_SEARCH_OBJECT:
284                 right_name = ast_sorcery_object_get_id(right->object);
285                 /* Fall through */
286         case OBJ_SEARCH_KEY:
287                 cmp = strcmp(ast_sorcery_object_get_id(left->object), right_name);
288                 break;
289         case OBJ_SEARCH_PARTIAL_KEY:
290                 cmp = strncmp(ast_sorcery_object_get_id(left->object), right_name, strlen(right_name));
291                 break;
292         }
293         return cmp ? 0 : CMP_MATCH;
294 }
295
296 /*!
297  * \internal
298  * \brief Destructor function for a sorcery memory cache
299  *
300  * \param obj A sorcery memory cache
301  */
302 static void sorcery_memory_cache_destructor(void *obj)
303 {
304         struct sorcery_memory_cache *cache = obj;
305
306         ast_free(cache->name);
307         ao2_cleanup(cache->objects);
308         if (cache->object_heap) {
309                 ast_heap_destroy(cache->object_heap);
310         }
311 }
312
313 /*!
314  * \internal
315  * \brief Destructor function for sorcery memory cached objects
316  *
317  * \param obj A sorcery memory cached object
318  */
319 static void sorcery_memory_cached_object_destructor(void *obj)
320 {
321         struct sorcery_memory_cached_object *cached = obj;
322
323         ao2_cleanup(cached->object);
324 }
325
326 static int schedule_cache_expiration(struct sorcery_memory_cache *cache);
327
328 /*!
329  * \internal
330  * \brief Remove an object from the cache.
331  *
332  * This removes the item from both the hashtable and the heap.
333  *
334  * \pre cache->objects is write-locked
335  *
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.
339  *
340  * \retval 0 Success
341  * \retval non-zero Failure
342  */
343 static int remove_from_cache(struct sorcery_memory_cache *cache, const char *id, int reschedule)
344 {
345         struct sorcery_memory_cached_object *hash_object;
346         struct sorcery_memory_cached_object *oldest_object;
347         struct sorcery_memory_cached_object *heap_object;
348
349         hash_object = ao2_find(cache->objects, id,
350                 OBJ_SEARCH_KEY | OBJ_UNLINK | OBJ_NOLOCK);
351         if (!hash_object) {
352                 return -1;
353         }
354         oldest_object = ast_heap_peek(cache->object_heap, 1);
355         heap_object = ast_heap_remove(cache->object_heap, hash_object);
356
357         ast_assert(heap_object == hash_object);
358
359         ao2_ref(hash_object, -1);
360
361         if (reschedule && (oldest_object == heap_object)) {
362                 schedule_cache_expiration(cache);
363         }
364
365         return 0;
366 }
367
368 /*!
369  * \internal
370  * \brief Scheduler callback invoked to expire old objects
371  *
372  * \param data The opaque callback data (in our case, the memory cache)
373  */
374 static int expire_objects_from_cache(const void *data)
375 {
376         struct sorcery_memory_cache *cache = (struct sorcery_memory_cache *)data;
377         struct sorcery_memory_cached_object *cached;
378
379         ao2_wrlock(cache->objects);
380
381         cache->expire_id = -1;
382
383         /* This is an optimization for objects which have been cached close to eachother */
384         while ((cached = ast_heap_peek(cache->object_heap, 1))) {
385                 int expiration;
386
387                 expiration = ast_tvdiff_ms(ast_tvadd(cached->created, ast_samp2tv(cache->object_lifetime_maximum, 1)), ast_tvnow());
388
389                 /* If the current oldest object has not yet expired stop and reschedule for it */
390                 if (expiration > 0) {
391                         break;
392                 }
393
394                 remove_from_cache(cache, ast_sorcery_object_get_id(cached->object), 0);
395         }
396
397         schedule_cache_expiration(cache);
398
399         ao2_unlock(cache->objects);
400
401         ao2_ref(cache, -1);
402
403         return 0;
404 }
405
406 /*!
407  * \internal
408  * \brief Schedule a callback for cached object expiration.
409  *
410  * \pre cache->objects is write-locked
411  *
412  * \param cache The cache that is having its callback scheduled.
413  *
414  * \retval 0 success
415  * \retval -1 failure
416  */
417 static int schedule_cache_expiration(struct sorcery_memory_cache *cache)
418 {
419         struct sorcery_memory_cached_object *cached;
420         int expiration = 0;
421
422         if (!cache->object_lifetime_maximum) {
423                 return 0;
424         }
425
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.
429                  */
430                 if (ast_sched_del(sched, cache->expire_id)) {
431                         return 0;
432                 }
433
434                 /* Since it successfully cancelled we need to drop the ref to the cache it had */
435                 ao2_ref(cache, -1);
436                 cache->expire_id = -1;
437         }
438
439         cached = ast_heap_peek(cache->object_heap, 1);
440         if (!cached) {
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);
446 #endif
447                 return 0;
448         }
449
450         expiration = MAX(ast_tvdiff_ms(ast_tvadd(cached->created, ast_samp2tv(cache->object_lifetime_maximum, 1)), ast_tvnow()),
451                 1);
452
453         cache->expire_id = ast_sched_add(sched, expiration, expire_objects_from_cache, ao2_bump(cache));
454         if (cache->expire_id < 0) {
455                 ao2_ref(cache, -1);
456                 return -1;
457         }
458
459         return 0;
460 }
461
462 /*!
463  * \internal
464  * \brief Remove the oldest item from the cache.
465  *
466  * \pre cache->objects is write-locked
467  *
468  * \param cache The cache from which to remove the oldest object
469  *
470  * \retval 0 Success
471  * \retval non-zero Failure
472  */
473 static int remove_oldest_from_cache(struct sorcery_memory_cache *cache)
474 {
475         struct sorcery_memory_cached_object *heap_old_object;
476         struct sorcery_memory_cached_object *hash_old_object;
477
478         heap_old_object = ast_heap_pop(cache->object_heap);
479         if (!heap_old_object) {
480                 return -1;
481         }
482         hash_old_object = ao2_find(cache->objects, heap_old_object,
483                 OBJ_SEARCH_OBJECT | OBJ_UNLINK | OBJ_NOLOCK);
484
485         ast_assert(heap_old_object == hash_old_object);
486
487         ao2_ref(hash_old_object, -1);
488
489         schedule_cache_expiration(cache);
490
491         return 0;
492 }
493
494 /*!
495  * \internal
496  * \brief Add a new object to the cache.
497  *
498  * \pre cache->objects is write-locked
499  *
500  * \param cache The cache in which to add the new object
501  * \param cached_object The object to add to the cache
502  *
503  * \retval 0 Success
504  * \retval non-zero Failure
505  */
506 static int add_to_cache(struct sorcery_memory_cache *cache,
507                 struct sorcery_memory_cached_object *cached_object)
508 {
509         if (!ao2_link_flags(cache->objects, cached_object, OBJ_NOLOCK)) {
510                 return -1;
511         }
512
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);
516                 return -1;
517         }
518
519         if (cache->expire_id == -1) {
520                 schedule_cache_expiration(cache);
521         }
522
523         return 0;
524 }
525
526 /*!
527  * \internal
528  * \brief Callback function to cache an object in a memory cache
529  *
530  * \param sorcery The sorcery instance
531  * \param data The sorcery memory cache
532  * \param object The object to cache
533  *
534  * \retval 0 success
535  * \retval -1 failure
536  */
537 static int sorcery_memory_cache_create(const struct ast_sorcery *sorcery, void *data, void *object)
538 {
539         struct sorcery_memory_cache *cache = data;
540         struct sorcery_memory_cached_object *cached;
541
542         cached = ao2_alloc(sizeof(*cached), sorcery_memory_cached_object_destructor);
543         if (!cached) {
544                 return -1;
545         }
546         cached->object = ao2_bump(object);
547         cached->created = ast_tvnow();
548         cached->stale_update_sched_id = -1;
549
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.
554          */
555
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));
562                         ao2_ref(cached, -1);
563                         ao2_unlock(cache->objects);
564                         return -1;
565                 }
566         }
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));
570                 ao2_ref(cached, -1);
571                 ao2_unlock(cache->objects);
572                 return -1;
573         }
574         ao2_unlock(cache->objects);
575
576         ao2_ref(cached, -1);
577         return 0;
578 }
579
580 struct stale_update_task_data {
581         struct ast_sorcery *sorcery;
582         struct sorcery_memory_cache *cache;
583         void *object;
584 };
585
586 static void stale_update_task_data_destructor(void *obj)
587 {
588         struct stale_update_task_data *task_data = obj;
589
590         ao2_cleanup(task_data->cache);
591         ao2_cleanup(task_data->object);
592         ast_sorcery_unref(task_data->sorcery);
593 }
594
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)
597 {
598         struct stale_update_task_data *task_data;
599
600         task_data = ao2_alloc_options(sizeof(*task_data), stale_update_task_data_destructor,
601                 AO2_ALLOC_OPT_LOCK_NOLOCK);
602         if (!task_data) {
603                 return NULL;
604         }
605
606         task_data->sorcery = ao2_bump(sorcery);
607         task_data->cache = ao2_bump(cache);
608         task_data->object = ao2_bump(object);
609
610         return task_data;
611 }
612
613 static int stale_item_update(const void *data)
614 {
615         struct stale_update_task_data *task_data = (struct stale_update_task_data *) data;
616         void *object;
617
618         start_stale_update();
619
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));
623         if (!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,
628                         task_data->object);
629         } else {
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,
634                         object);
635         }
636
637         ao2_ref(task_data, -1);
638         end_stale_update();
639
640         return 0;
641 }
642
643 /*!
644  * \internal
645  * \brief Callback function to retrieve an object from a memory cache
646  *
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
651  *
652  * \retval non-NULL success
653  * \retval NULL failure
654  */
655 static void *sorcery_memory_cache_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id)
656 {
657         struct sorcery_memory_cache *cache = data;
658         struct sorcery_memory_cached_object *cached;
659         void *object;
660
661         if (is_stale_update()) {
662                 return NULL;
663         }
664
665         cached = ao2_find(cache->objects, id, OBJ_SEARCH_KEY);
666         if (!cached) {
667                 return NULL;
668         }
669
670         if (cache->object_lifetime_stale) {
671                 struct timeval elapsed;
672
673                 elapsed = ast_tvsub(ast_tvnow(), cached->created);
674                 if (elapsed.tv_sec > cache->object_lifetime_stale) {
675                         ao2_lock(cached);
676                         if (cached->stale_update_sched_id == -1) {
677                                 struct stale_update_task_data *task_data;
678
679                                 task_data = stale_update_task_data_alloc((struct ast_sorcery *)sorcery, cache,
680                                         type, cached->object);
681                                 if (task_data) {
682                                         ast_debug(1, "Cached sorcery object type '%s' ID '%s' is stale. Refreshing\n",
683                                                 type, id);
684                                         cached->stale_update_sched_id = ast_sched_add(sched, 1, stale_item_update, task_data);
685                                 } else {
686                                         ast_log(LOG_ERROR, "Unable to update stale cached object type '%s', ID '%s'.\n",
687                                                 type, id);
688                                 }
689                         }
690                         ao2_unlock(cached);
691                 }
692         }
693
694         object = ao2_bump(cached->object);
695         ao2_ref(cached, -1);
696
697         return object;
698 }
699
700 /*!
701  * \internal
702  * \brief Callback function to finish configuring the memory cache and to prefetch objects
703  *
704  * \param data The sorcery memory cache
705  * \param sorcery The sorcery instance
706  * \param type The type of object being loaded
707  */
708 static void sorcery_memory_cache_load(void *data, const struct ast_sorcery *sorcery, const char *type)
709 {
710         struct sorcery_memory_cache *cache = data;
711
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);
715         }
716
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);
720 }
721
722 /*!
723  * \internal
724  * \brief Callback function to expire objects from the memory cache on reload (if configured)
725  *
726  * \param data The sorcery memory cache
727  * \param sorcery The sorcery instance
728  * \param type The type of object being reloaded
729  */
730 static void sorcery_memory_cache_reload(void *data, const struct ast_sorcery *sorcery, const char *type)
731 {
732 }
733
734 /*!
735  * \internal
736  * \brief Function used to take an unsigned integer based configuration option and parse it
737  *
738  * \param value The string value of the configuration option
739  * \param result The unsigned integer to place the result in
740  *
741  * \retval 0 failure
742  * \retval 1 success
743  */
744 static int configuration_parse_unsigned_integer(const char *value, unsigned int *result)
745 {
746         if (ast_strlen_zero(value) || !strncmp(value, "-", 1)) {
747                 return 0;
748         }
749
750         return sscanf(value, "%30u", result);
751 }
752
753 static int age_cmp(void *a, void *b)
754 {
755         return ast_tvcmp(((struct sorcery_memory_cached_object *) b)->created,
756                         ((struct sorcery_memory_cached_object *) a)->created);
757 }
758
759 /*!
760  * \internal
761  * \brief Callback function to create a new sorcery memory cache using provided configuration
762  *
763  * \param data A stringified configuration for the memory cache
764  *
765  * \retval non-NULL success
766  * \retval NULL failure
767  */
768 static void *sorcery_memory_cache_open(const char *data)
769 {
770         char *options = ast_strdup(data), *option;
771         RAII_VAR(struct sorcery_memory_cache *, cache, NULL, ao2_cleanup);
772
773         cache = ao2_alloc_options(sizeof(*cache), sorcery_memory_cache_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
774         if (!cache) {
775                 return NULL;
776         }
777
778         cache->expire_id = -1;
779
780         /* If no configuration options have been provided this memory cache will operate in a default
781          * configuration.
782          */
783         while (!ast_strlen_zero(options) && (option = strsep(&options, ","))) {
784                 char *name = strsep(&option, "="), *value = option;
785
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");
789                                 return NULL;
790                         }
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",
796                                         value);
797                                 return NULL;
798                         }
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",
802                                         value);
803                                 return NULL;
804                         }
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",
808                                         value);
809                                 return NULL;
810                         }
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);
815                 } else {
816                         ast_log(LOG_ERROR, "Unsupported option '%s' used for memory cache\n", name);
817                         return NULL;
818                 }
819         }
820
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");
826                 return NULL;
827         }
828
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");
833                 return NULL;
834         }
835
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.
840          */
841
842         /* This is done as RAII_VAR will drop the reference */
843         return ao2_bump(cache);
844 }
845
846 /*!
847  * \internal
848  * \brief Callback function to delete an object from a memory cache
849  *
850  * \param sorcery The sorcery instance
851  * \param data The sorcery memory cache
852  * \param object The object to cache
853  *
854  * \retval 0 success
855  * \retval -1 failure
856  */
857 static int sorcery_memory_cache_delete(const struct ast_sorcery *sorcery, void *data, void *object)
858 {
859         struct sorcery_memory_cache *cache = data;
860         int res;
861
862         ao2_wrlock(cache->objects);
863         res = remove_from_cache(cache, ast_sorcery_object_get_id(object), 1);
864         ao2_unlock(cache->objects);
865
866         if (res) {
867                 ast_log(LOG_ERROR, "Unable to delete object '%s' from sorcery cache\n", ast_sorcery_object_get_id(object));
868         }
869
870         return res;
871 }
872
873 /*!
874  * \internal
875  * \brief Callback function to terminate a memory cache
876  *
877  * \param data The sorcery memory cache
878  */
879 static void sorcery_memory_cache_close(void *data)
880 {
881         struct sorcery_memory_cache *cache = data;
882
883         /* This can occur if a cache is created but never loaded */
884         if (!ast_strlen_zero(cache->name)) {
885                 ao2_unlink(caches, cache);
886         }
887
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.
892                  */
893                 ao2_wrlock(cache->objects);
894                 ao2_callback(cache->objects, OBJ_UNLINK | OBJ_NOLOCK | OBJ_NODATA | OBJ_MULTIPLE,
895                         NULL, NULL);
896                 AST_SCHED_DEL_UNREF(sched, cache->expire_id, ao2_ref(cache, -1));
897                 ao2_unlock(cache->objects);
898         }
899
900         ao2_ref(cache, -1);
901 }
902
903 #ifdef TEST_FRAMEWORK
904
905 /*! \brief Dummy sorcery object */
906 struct test_sorcery_object {
907         SORCERY_OBJECT(details);
908 };
909
910 /*!
911  * \internal
912  * \brief Allocator for test object
913  *
914  * \param id The identifier for the object
915  *
916  * \retval non-NULL success
917  * \retval NULL failure
918  */
919 static void *test_sorcery_object_alloc(const char *id)
920 {
921         return ast_sorcery_generic_alloc(sizeof(struct test_sorcery_object), NULL);
922 }
923
924 /*!
925  * \internal
926  * \brief Allocator for test sorcery instance
927  *
928  * \retval non-NULL success
929  * \retval NULL failure
930  */
931 static struct ast_sorcery *alloc_and_initialize_sorcery(void)
932 {
933         struct ast_sorcery *sorcery;
934
935         if (!(sorcery = ast_sorcery_open())) {
936                 return NULL;
937         }
938
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);
942                 return NULL;
943         }
944
945         return sorcery;
946 }
947
948 AST_TEST_DEFINE(open_with_valid_options)
949 {
950         int res = AST_TEST_PASS;
951         struct sorcery_memory_cache *cache;
952
953         switch (cmd) {
954         case TEST_INIT:
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;
964         case TEST_EXECUTE:
965                 break;
966         }
967
968         cache = sorcery_memory_cache_open("");
969         if (!cache) {
970                 ast_test_status_update(test, "Failed to create a sorcery memory cache using default configuration\n");
971                 res = AST_TEST_FAIL;
972         } else {
973                 sorcery_memory_cache_close(cache);
974         }
975
976         cache = sorcery_memory_cache_open("maximum_objects=10");
977         if (!cache) {
978                 ast_test_status_update(test, "Failed to create a sorcery memory cache with a maximum object count of 10\n");
979                 res = AST_TEST_FAIL;
980         } else {
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);
984                 }
985                 sorcery_memory_cache_close(cache);
986         }
987
988         cache = sorcery_memory_cache_open("object_lifetime_maximum=60");
989         if (!cache) {
990                 ast_test_status_update(test, "Failed to create a sorcery memory cache with a maximum object lifetime of 60\n");
991                 res = AST_TEST_FAIL;
992         } else {
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);
996                 }
997                 sorcery_memory_cache_close(cache);
998         }
999
1000         cache = sorcery_memory_cache_open("object_lifetime_stale=90");
1001         if (!cache) {
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;
1004         } else {
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);
1008                 }
1009                 sorcery_memory_cache_close(cache);
1010         }
1011
1012
1013         return res;
1014 }
1015
1016 AST_TEST_DEFINE(open_with_invalid_options)
1017 {
1018         int res = AST_TEST_PASS;
1019         struct sorcery_memory_cache *cache;
1020
1021         switch (cmd) {
1022         case TEST_INIT:
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;
1035         case TEST_EXECUTE:
1036                 break;
1037         }
1038
1039         cache = sorcery_memory_cache_open("name=");
1040         if (cache) {
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;
1044         }
1045
1046         cache = sorcery_memory_cache_open("maximum_objects=-1");
1047         if (cache) {
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;
1051         }
1052
1053         cache = sorcery_memory_cache_open("maximum_objects=toast");
1054         if (cache) {
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;
1058         }
1059
1060         cache = sorcery_memory_cache_open("object_lifetime_maximum=-1");
1061         if (cache) {
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;
1065         }
1066
1067         cache = sorcery_memory_cache_open("object_lifetime_maximum=toast");
1068         if (cache) {
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;
1072         }
1073
1074         cache = sorcery_memory_cache_open("object_lifetime_stale=-1");
1075         if (cache) {
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;
1079         }
1080
1081         cache = sorcery_memory_cache_open("object_lifetime_stale=toast");
1082         if (cache) {
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;
1086         }
1087
1088         cache = sorcery_memory_cache_open("tacos");
1089         if (cache) {
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;
1093         }
1094
1095         return res;
1096 }
1097
1098 AST_TEST_DEFINE(create_and_retrieve)
1099 {
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);
1105
1106         switch (cmd) {
1107         case TEST_INIT:
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;
1118         case TEST_EXECUTE:
1119                 break;
1120         }
1121
1122         cache = sorcery_memory_cache_open("");
1123         if (!cache) {
1124                 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
1125                 goto cleanup;
1126         }
1127
1128         if (ao2_container_count(cache->objects)) {
1129                 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
1130                 goto cleanup;
1131         }
1132
1133         sorcery = alloc_and_initialize_sorcery();
1134         if (!sorcery) {
1135                 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
1136                 goto cleanup;
1137         }
1138
1139         object = ast_sorcery_alloc(sorcery, "test", "test");
1140         if (!object) {
1141                 ast_test_status_update(test, "Failed to allocate a test object\n");
1142                 goto cleanup;
1143         }
1144
1145         sorcery_memory_cache_create(sorcery, cache, object);
1146
1147         if (!ao2_container_count(cache->objects)) {
1148                 ast_test_status_update(test, "Added test object to memory cache but cache remains empty\n");
1149                 goto cleanup;
1150         }
1151
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");
1155                 goto cleanup;
1156         }
1157
1158         if (cached_object != object) {
1159                 ast_test_status_update(test, "Object retrieved from memory cached is not the one we cached\n");
1160                 goto cleanup;
1161         }
1162
1163         res = AST_TEST_PASS;
1164
1165 cleanup:
1166         if (cache) {
1167                 sorcery_memory_cache_close(cache);
1168         }
1169         if (sorcery) {
1170                 ast_sorcery_unref(sorcery);
1171         }
1172
1173         return res;
1174 }
1175
1176 AST_TEST_DEFINE(update)
1177 {
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);
1184
1185         switch (cmd) {
1186         case TEST_INIT:
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;
1200         case TEST_EXECUTE:
1201                 break;
1202         }
1203
1204         cache = sorcery_memory_cache_open("");
1205         if (!cache) {
1206                 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
1207                 goto cleanup;
1208         }
1209
1210         if (ao2_container_count(cache->objects)) {
1211                 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
1212                 goto cleanup;
1213         }
1214
1215         sorcery = alloc_and_initialize_sorcery();
1216         if (!sorcery) {
1217                 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
1218                 goto cleanup;
1219         }
1220
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");
1224                 goto cleanup;
1225         }
1226
1227         sorcery_memory_cache_create(sorcery, cache, original_object);
1228
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");
1232                 goto cleanup;
1233         }
1234
1235         sorcery_memory_cache_create(sorcery, cache, updated_object);
1236
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));
1240                 goto cleanup;
1241         }
1242
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");
1246                 goto cleanup;
1247         }
1248
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");
1251                 goto cleanup;
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");
1254                 goto cleanup;
1255         }
1256
1257         res = AST_TEST_PASS;
1258
1259 cleanup:
1260         if (cache) {
1261                 sorcery_memory_cache_close(cache);
1262         }
1263         if (sorcery) {
1264                 ast_sorcery_unref(sorcery);
1265         }
1266
1267         return res;
1268 }
1269
1270 AST_TEST_DEFINE(delete)
1271 {
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);
1277
1278         switch (cmd) {
1279         case TEST_INIT:
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;
1292         case TEST_EXECUTE:
1293                 break;
1294         }
1295
1296         cache = sorcery_memory_cache_open("");
1297         if (!cache) {
1298                 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
1299                 goto cleanup;
1300         }
1301
1302         if (ao2_container_count(cache->objects)) {
1303                 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
1304                 goto cleanup;
1305         }
1306
1307         sorcery = alloc_and_initialize_sorcery();
1308         if (!sorcery) {
1309                 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
1310                 goto cleanup;
1311         }
1312
1313         object = ast_sorcery_alloc(sorcery, "test", "test");
1314         if (!object) {
1315                 ast_test_status_update(test, "Failed to allocate a test object\n");
1316                 goto cleanup;
1317         }
1318
1319         sorcery_memory_cache_create(sorcery, cache, object);
1320
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");
1323                 goto cleanup;
1324         }
1325
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");
1329                 goto cleanup;
1330         }
1331
1332         ao2_ref(cached_object, -1);
1333         cached_object = NULL;
1334
1335         sorcery_memory_cache_delete(sorcery, cache, object);
1336
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");
1340                 goto cleanup;
1341         }
1342
1343         res = AST_TEST_PASS;
1344
1345 cleanup:
1346         if (cache) {
1347                 sorcery_memory_cache_close(cache);
1348         }
1349         if (sorcery) {
1350                 ast_sorcery_unref(sorcery);
1351         }
1352
1353         return res;
1354 }
1355
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)
1358 {
1359         int i;
1360         int res = 0;
1361         RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
1362
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",
1367                                         in_cache[i]);
1368                         res = -1;
1369                 }
1370                 ao2_ref(cached_object, -1);
1371         }
1372
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",
1377                                         not_in_cache[i]);
1378                         ao2_ref(cached_object, -1);
1379                         res = -1;
1380                 }
1381         }
1382
1383         return res;
1384 }
1385
1386 AST_TEST_DEFINE(maximum_objects)
1387 {
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];
1397
1398         switch (cmd) {
1399         case TEST_INIT:
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;
1416         case TEST_EXECUTE:
1417                 break;
1418         }
1419
1420         cache = sorcery_memory_cache_open("maximum_objects=2");
1421         if (!cache) {
1422                 ast_test_status_update(test, "Failed to create a sorcery memory cache with maximum_objects=2\n");
1423                 goto cleanup;
1424         }
1425
1426         if (ao2_container_count(cache->objects)) {
1427                 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
1428                 goto cleanup;
1429         }
1430
1431         sorcery = alloc_and_initialize_sorcery();
1432         if (!sorcery) {
1433                 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
1434                 goto cleanup;
1435         }
1436
1437         alice = ast_sorcery_alloc(sorcery, "test", "alice");
1438         bob = ast_sorcery_alloc(sorcery, "test", "bob");
1439         charlie = ast_sorcery_alloc(sorcery, "test", "charlie");
1440
1441         if (!alice || !bob || !charlie) {
1442                 ast_test_status_update(test, "Failed to allocate sorcery object(s)\n");
1443                 goto cleanup;
1444         }
1445
1446         sorcery_memory_cache_create(sorcery, cache, alice);
1447         in_cache[0] = "alice";
1448         in_cache[1] = NULL;
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)) {
1452                 goto cleanup;
1453         }
1454
1455         /* Delays are added to ensure that we are not adding cache entries within the
1456          * same microsecond
1457          */
1458         usleep(1000);
1459
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)) {
1466                 goto cleanup;
1467         }
1468
1469         usleep(1000);
1470
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)) {
1477                 goto cleanup;
1478         }
1479         usleep(1000);
1480
1481         sorcery_memory_cache_delete(sorcery, cache, charlie);
1482         in_cache[0] = "bob";
1483         in_cache[1] = NULL;
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)) {
1487                 goto cleanup;
1488         }
1489         usleep(1000);
1490
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)) {
1497                 goto cleanup;
1498         }
1499
1500         res = AST_TEST_PASS;
1501
1502 cleanup:
1503         if (cache) {
1504                 sorcery_memory_cache_close(cache);
1505         }
1506         if (sorcery) {
1507                 ast_sorcery_unref(sorcery);
1508         }
1509
1510         return res;
1511 }
1512
1513 AST_TEST_DEFINE(expiration)
1514 {
1515         int res = AST_TEST_FAIL;
1516         struct ast_sorcery *sorcery = NULL;
1517         struct sorcery_memory_cache *cache = NULL;
1518         int i;
1519
1520         switch (cmd) {
1521         case TEST_INIT:
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;
1531         case TEST_EXECUTE:
1532                 break;
1533         }
1534
1535         cache = sorcery_memory_cache_open("object_lifetime_maximum=5");
1536         if (!cache) {
1537                 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
1538                 goto cleanup;
1539         }
1540
1541         sorcery = alloc_and_initialize_sorcery();
1542         if (!sorcery) {
1543                 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
1544                 goto cleanup;
1545         }
1546
1547         cache->cache_notify = 1;
1548         ast_mutex_init(&cache->lock);
1549         ast_cond_init(&cache->cond, NULL);
1550
1551         for (i = 0; i < 5; ++i) {
1552                 char uuid[AST_UUID_STR_LEN];
1553                 void *object;
1554
1555                 object = ast_sorcery_alloc(sorcery, "test", ast_uuid_generate_str(uuid, sizeof(uuid)));
1556                 if (!object) {
1557                         ast_test_status_update(test, "Failed to allocate test object for expiration\n");
1558                         goto cleanup;
1559                 }
1560
1561                 sorcery_memory_cache_create(sorcery, cache, object);
1562
1563                 ao2_ref(object, -1);
1564         }
1565
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,
1572                 };
1573
1574                 if (ast_cond_timedwait(&cache->cond, &cache->lock, &end) == ETIMEDOUT) {
1575                         break;
1576                 }
1577         }
1578         ast_mutex_unlock(&cache->lock);
1579
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");
1582                 goto cleanup;
1583         }
1584
1585         res = AST_TEST_PASS;
1586
1587 cleanup:
1588         if (cache) {
1589                 if (cache->cache_notify) {
1590                         ast_cond_destroy(&cache->cond);
1591                         ast_mutex_destroy(&cache->lock);
1592                 }
1593                 sorcery_memory_cache_close(cache);
1594         }
1595         if (sorcery) {
1596                 ast_sorcery_unref(sorcery);
1597         }
1598
1599         return res;
1600 }
1601
1602 /*!
1603  * \brief Backend data that the mock sorcery wizard uses to create objects
1604  */
1605 static struct backend_data {
1606         /*! An arbitrary data field */
1607         int salt;
1608         /*! Another arbitrary data field */
1609         int pepper;
1610         /*! Indicates whether the backend has data */
1611         int exists;
1612 } *real_backend_data;
1613
1614 /*!
1615  * \brief Sorcery object created based on backend data
1616  */
1617 struct test_data {
1618         SORCERY_OBJECT(details);
1619         /*! Mirrors the backend data's salt field */
1620         int salt;
1621         /*! Mirrors the backend data's pepper field */
1622         int pepper;
1623 };
1624
1625 /*!
1626  * \brief Allocation callback for test_data sorcery object
1627  */
1628 static void *test_data_alloc(const char *id) {
1629         return ast_sorcery_generic_alloc(sizeof(struct test_data), NULL);
1630 }
1631
1632 /*!
1633  * \brief Callback for retrieving sorcery object by ID
1634  *
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.
1639  *
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".
1644  *
1645  * \retval NULL Backend data does not exist
1646  * \retval non-NULL An object representing the backend data
1647  */
1648 static void *mock_retrieve_id(const struct ast_sorcery *sorcery, void *data,
1649                 const char *type, const char *id)
1650 {
1651         struct test_data *b_data;
1652
1653         if (!real_backend_data->exists) {
1654                 return NULL;
1655         }
1656
1657         b_data = ast_sorcery_alloc(sorcery, type, id);
1658         if (!b_data) {
1659                 return NULL;
1660         }
1661
1662         b_data->salt = real_backend_data->salt;
1663         b_data->pepper = real_backend_data->pepper;
1664         return b_data;
1665 }
1666
1667 /*!
1668  * \brief A mock sorcery wizard used for the stale test
1669  */
1670 static struct ast_sorcery_wizard mock_wizard = {
1671         .name = "mock",
1672         .retrieve_id = mock_retrieve_id,
1673 };
1674
1675 /*!
1676  * \brief Wait for the cache to be updated after a stale object is retrieved.
1677  *
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.
1682  *
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
1686  *
1687  * \retval 0 Successfully retrieved a new object from the cache
1688  * \retval non-zero Failed to retrieve a new object from the cache
1689  */
1690 static int wait_for_cache_update(const struct ast_sorcery *sorcery,
1691                 void *previous_object, struct test_data **new_object)
1692 {
1693         struct timeval start = ast_tvnow();
1694
1695         while (ast_remaining_ms(start, 5000) > 0) {
1696                 void *object;
1697
1698                 object = ast_sorcery_retrieve_by_id(sorcery, "test", "test");
1699                 if (object != previous_object) {
1700                         *new_object = object;
1701                         return 0;
1702                 }
1703                 ao2_cleanup(object);
1704         }
1705
1706         return -1;
1707 }
1708
1709 AST_TEST_DEFINE(stale)
1710 {
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 },
1718         };
1719         struct backend_data initial = {
1720                 .salt = 0,
1721                 .pepper = 0,
1722                 .exists = 1,
1723         };
1724         int i;
1725
1726         switch (cmd) {
1727         case TEST_INIT:
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;
1746         case TEST_EXECUTE:
1747                 break;
1748         }
1749
1750         ast_sorcery_wizard_register(&mock_wizard);
1751
1752         sorcery = ast_sorcery_open();
1753         if (!sorcery) {
1754                 ast_test_status_update(test, "Failed to create sorcery instance\n");
1755                 goto cleanup;
1756         }
1757
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);
1762
1763         /* Prepopulate the cache */
1764         real_backend_data = &initial;
1765
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");
1769                 goto cleanup;
1770         }
1771         ao2_ref(backend_object, -1);
1772
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);
1777
1778                 real_backend_data = &iterations[i];
1779
1780                 ast_test_status_update(test, "Begininning iteration %d\n", i);
1781
1782                 cache_fresh = ast_sorcery_retrieve_by_id(sorcery, "test", "test");
1783                 if (!cache_fresh) {
1784                         ast_test_status_update(test, "Unable to retrieve fresh cached object\n");
1785                         goto cleanup;
1786                 }
1787
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");
1790                         goto cleanup;
1791                 }
1792
1793                 sleep(5);
1794
1795                 cache_stale = ast_sorcery_retrieve_by_id(sorcery, "test", "test");
1796                 if (!cache_stale) {
1797                         ast_test_status_update(test, "Unable to retrieve stale cached object\n");
1798                         goto cleanup;
1799                 }
1800
1801                 if (cache_stale != cache_fresh) {
1802                         ast_test_status_update(test, "Stale cache hit retrieved different object than fresh cache hit\n");
1803                         goto cleanup;
1804                 }
1805
1806                 if (wait_for_cache_update(sorcery, cache_stale, &cache_new)) {
1807                         ast_test_status_update(test, "Cache was not updated\n");
1808                         goto cleanup;
1809                 }
1810
1811                 if (iterations[i].exists) {
1812                         if (!cache_new) {
1813                                 ast_test_status_update(test, "Failed to retrieve item from cache when there should be one present\n");
1814                                 goto cleanup;
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");
1818                                 goto cleanup;
1819                         }
1820                 } else if (cache_new) {
1821                         ast_test_status_update(test, "Retrieved a cached item when there should not have been one present\n");
1822                         goto cleanup;
1823                 }
1824         }
1825
1826         res = AST_TEST_PASS;
1827
1828 cleanup:
1829         if (sorcery) {
1830                 ast_sorcery_unref(sorcery);
1831         }
1832         ast_sorcery_wizard_unregister(&mock_wizard);
1833         return res;
1834 }
1835
1836 #endif
1837
1838 static int unload_module(void)
1839 {
1840         if (sched) {
1841                 ast_sched_context_destroy(sched);
1842                 sched = NULL;
1843         }
1844
1845         ao2_cleanup(caches);
1846
1847         ast_sorcery_wizard_unregister(&memory_cache_object_wizard);
1848
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);
1857
1858         return 0;
1859 }
1860
1861 static int load_module(void)
1862 {
1863         sched = ast_sched_context_create();
1864         if (!sched) {
1865                 ast_log(LOG_ERROR, "Failed to create scheduler for cache management\n");
1866                 unload_module();
1867                 return AST_MODULE_LOAD_DECLINE;
1868         }
1869
1870         if (ast_sched_start_thread(sched)) {
1871                 ast_log(LOG_ERROR, "Failed to create scheduler thread for cache management\n");
1872                 unload_module();
1873                 return AST_MODULE_LOAD_DECLINE;
1874         }
1875
1876         caches = ao2_container_alloc(CACHES_CONTAINER_BUCKET_SIZE, sorcery_memory_cache_hash,
1877                 sorcery_memory_cache_cmp);
1878         if (!caches) {
1879                 ast_log(LOG_ERROR, "Failed to create container for configured caches\n");
1880                 unload_module();
1881                 return AST_MODULE_LOAD_DECLINE;
1882         }
1883
1884         if (ast_sorcery_wizard_register(&memory_cache_object_wizard)) {
1885                 unload_module();
1886                 return AST_MODULE_LOAD_DECLINE;
1887         }
1888
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);
1897
1898         return AST_MODULE_LOAD_SUCCESS;
1899 }
1900
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,
1906 );