res_sorcery_memory_cache: Add support for object_lifetime_maximum.
[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 };
83
84 static void *sorcery_memory_cache_open(const char *data);
85 static int sorcery_memory_cache_create(const struct ast_sorcery *sorcery, void *data, void *object);
86 static void sorcery_memory_cache_load(void *data, const struct ast_sorcery *sorcery, const char *type);
87 static void sorcery_memory_cache_reload(void *data, const struct ast_sorcery *sorcery, const char *type);
88 static void *sorcery_memory_cache_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type,
89         const char *id);
90 static int sorcery_memory_cache_delete(const struct ast_sorcery *sorcery, void *data, void *object);
91 static void sorcery_memory_cache_close(void *data);
92
93 static struct ast_sorcery_wizard memory_cache_object_wizard = {
94         .name = "memory_cache",
95         .open = sorcery_memory_cache_open,
96         .create = sorcery_memory_cache_create,
97         .update = sorcery_memory_cache_create,
98         .delete = sorcery_memory_cache_delete,
99         .load = sorcery_memory_cache_load,
100         .reload = sorcery_memory_cache_reload,
101         .retrieve_id = sorcery_memory_cache_retrieve_id,
102         .close = sorcery_memory_cache_close,
103 };
104
105 /*! \brief The bucket size for the container of caches */
106 #define CACHES_CONTAINER_BUCKET_SIZE 53
107
108 /*! \brief The default bucket size for the container of objects in the cache */
109 #define CACHE_CONTAINER_BUCKET_SIZE 53
110
111 /*! \brief Height of heap for cache object heap. Allows 31 initial objects */
112 #define CACHE_HEAP_INIT_HEIGHT 5
113
114 /*! \brief Container of created caches */
115 static struct ao2_container *caches;
116
117 /*! \brief Scheduler for cache management */
118 static struct ast_sched_context *sched;
119
120 /*!
121  * \internal
122  * \brief Hashing function for the container holding caches
123  *
124  * \param obj A sorcery memory cache or name of one
125  * \param flags Hashing flags
126  *
127  * \return The hash of the memory cache name
128  */
129 static int sorcery_memory_cache_hash(const void *obj, int flags)
130 {
131         const struct sorcery_memory_cache *cache = obj;
132         const char *name = obj;
133         int hash;
134
135         switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
136         default:
137         case OBJ_SEARCH_OBJECT:
138                 name = cache->name;
139                 /* Fall through */
140         case OBJ_SEARCH_KEY:
141                 hash = ast_str_hash(name);
142                 break;
143         case OBJ_SEARCH_PARTIAL_KEY:
144                 /* Should never happen in hash callback. */
145                 ast_assert(0);
146                 hash = 0;
147                 break;
148         }
149         return hash;
150 }
151
152 /*!
153  * \internal
154  * \brief Comparison function for the container holding caches
155  *
156  * \param obj A sorcery memory cache
157  * \param arg A sorcery memory cache, or name of one
158  * \param flags Comparison flags
159  *
160  * \retval CMP_MATCH if the name is the same
161  * \retval 0 if the name does not match
162  */
163 static int sorcery_memory_cache_cmp(void *obj, void *arg, int flags)
164 {
165         const struct sorcery_memory_cache *left = obj;
166         const struct sorcery_memory_cache *right = arg;
167         const char *right_name = arg;
168         int cmp;
169
170         switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
171         default:
172         case OBJ_SEARCH_OBJECT:
173                 right_name = right->name;
174                 /* Fall through */
175         case OBJ_SEARCH_KEY:
176                 cmp = strcmp(left->name, right_name);
177                 break;
178         case OBJ_SEARCH_PARTIAL_KEY:
179                 cmp = strncmp(left->name, right_name, strlen(right_name));
180                 break;
181         }
182         return cmp ? 0 : CMP_MATCH;
183 }
184
185 /*!
186  * \internal
187  * \brief Hashing function for the container holding cached objects
188  *
189  * \param obj A cached object or id of one
190  * \param flags Hashing flags
191  *
192  * \return The hash of the cached object id
193  */
194 static int sorcery_memory_cached_object_hash(const void *obj, int flags)
195 {
196         const struct sorcery_memory_cached_object *cached = obj;
197         const char *name = obj;
198         int hash;
199
200         switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
201         default:
202         case OBJ_SEARCH_OBJECT:
203                 name = ast_sorcery_object_get_id(cached->object);
204                 /* Fall through */
205         case OBJ_SEARCH_KEY:
206                 hash = ast_str_hash(name);
207                 break;
208         case OBJ_SEARCH_PARTIAL_KEY:
209                 /* Should never happen in hash callback. */
210                 ast_assert(0);
211                 hash = 0;
212                 break;
213         }
214         return hash;
215 }
216
217 /*!
218  * \internal
219  * \brief Comparison function for the container holding cached objects
220  *
221  * \param obj A cached object
222  * \param arg A cached object, or id of one
223  * \param flags Comparison flags
224  *
225  * \retval CMP_MATCH if the id is the same
226  * \retval 0 if the id does not match
227  */
228 static int sorcery_memory_cached_object_cmp(void *obj, void *arg, int flags)
229 {
230         struct sorcery_memory_cached_object *left = obj;
231         struct sorcery_memory_cached_object *right = arg;
232         const char *right_name = arg;
233         int cmp;
234
235         switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
236         default:
237         case OBJ_SEARCH_OBJECT:
238                 right_name = ast_sorcery_object_get_id(right->object);
239                 /* Fall through */
240         case OBJ_SEARCH_KEY:
241                 cmp = strcmp(ast_sorcery_object_get_id(left->object), right_name);
242                 break;
243         case OBJ_SEARCH_PARTIAL_KEY:
244                 cmp = strncmp(ast_sorcery_object_get_id(left->object), right_name, strlen(right_name));
245                 break;
246         }
247         return cmp ? 0 : CMP_MATCH;
248 }
249
250 /*!
251  * \internal
252  * \brief Destructor function for a sorcery memory cache
253  *
254  * \param obj A sorcery memory cache
255  */
256 static void sorcery_memory_cache_destructor(void *obj)
257 {
258         struct sorcery_memory_cache *cache = obj;
259
260         ast_free(cache->name);
261         ao2_cleanup(cache->objects);
262         if (cache->object_heap) {
263                 ast_heap_destroy(cache->object_heap);
264         }
265 }
266
267 /*!
268  * \internal
269  * \brief Destructor function for sorcery memory cached objects
270  *
271  * \param obj A sorcery memory cached object
272  */
273 static void sorcery_memory_cached_object_destructor(void *obj)
274 {
275         struct sorcery_memory_cached_object *cached = obj;
276
277         ao2_cleanup(cached->object);
278 }
279
280 static int schedule_cache_expiration(struct sorcery_memory_cache *cache);
281
282 /*!
283  * \internal
284  * \brief Remove an object from the cache.
285  *
286  * This removes the item from both the hashtable and the heap.
287  *
288  * \pre cache->objects is write-locked
289  *
290  * \param cache The cache from which the object is being removed.
291  * \param id The sorcery object id of the object to remove.
292  * \param reschedule Reschedule cache expiration if this was the oldest object.
293  *
294  * \retval 0 Success
295  * \retval non-zero Failure
296  */
297 static int remove_from_cache(struct sorcery_memory_cache *cache, const char *id, int reschedule)
298 {
299         struct sorcery_memory_cached_object *hash_object;
300         struct sorcery_memory_cached_object *oldest_object;
301         struct sorcery_memory_cached_object *heap_object;
302
303         hash_object = ao2_find(cache->objects, id,
304                 OBJ_SEARCH_KEY | OBJ_UNLINK | OBJ_NOLOCK);
305         if (!hash_object) {
306                 return -1;
307         }
308         oldest_object = ast_heap_peek(cache->object_heap, 1);
309         heap_object = ast_heap_remove(cache->object_heap, hash_object);
310
311         ast_assert(heap_object == hash_object);
312
313         ao2_ref(hash_object, -1);
314
315         if (reschedule && (oldest_object == heap_object)) {
316                 schedule_cache_expiration(cache);
317         }
318
319         return 0;
320 }
321
322 /*!
323  * \internal
324  * \brief Scheduler callback invoked to expire old objects
325  *
326  * \param data The opaque callback data (in our case, the memory cache)
327  */
328 static int expire_objects_from_cache(const void *data)
329 {
330         struct sorcery_memory_cache *cache = (struct sorcery_memory_cache *)data;
331         struct sorcery_memory_cached_object *cached;
332
333         ao2_wrlock(cache->objects);
334
335         cache->expire_id = -1;
336
337         /* This is an optimization for objects which have been cached close to eachother */
338         while ((cached = ast_heap_peek(cache->object_heap, 1))) {
339                 int expiration;
340
341                 expiration = ast_tvdiff_ms(ast_tvadd(cached->created, ast_samp2tv(cache->object_lifetime_maximum, 1)), ast_tvnow());
342
343                 /* If the current oldest object has not yet expired stop and reschedule for it */
344                 if (expiration > 0) {
345                         break;
346                 }
347
348                 remove_from_cache(cache, ast_sorcery_object_get_id(cached->object), 0);
349         }
350
351         schedule_cache_expiration(cache);
352
353         ao2_unlock(cache->objects);
354
355         ao2_ref(cache, -1);
356
357         return 0;
358 }
359
360 /*!
361  * \internal
362  * \brief Schedule a callback for cached object expiration.
363  *
364  * \pre cache->objects is write-locked
365  *
366  * \param cache The cache that is having its callback scheduled.
367  *
368  * \retval 0 success
369  * \retval -1 failure
370  */
371 static int schedule_cache_expiration(struct sorcery_memory_cache *cache)
372 {
373         struct sorcery_memory_cached_object *cached;
374         int expiration = 0;
375
376         if (!cache->object_lifetime_maximum) {
377                 return 0;
378         }
379
380         if (cache->expire_id != -1) {
381                 /* If we can't unschedule this expiration then it is currently attempting to run,
382                  * so let it run - it just means that it'll be the one scheduling instead of us.
383                  */
384                 if (ast_sched_del(sched, cache->expire_id)) {
385                         return 0;
386                 }
387
388                 /* Since it successfully cancelled we need to drop the ref to the cache it had */
389                 ao2_ref(cache, -1);
390                 cache->expire_id = -1;
391         }
392
393         cached = ast_heap_peek(cache->object_heap, 1);
394         if (!cached) {
395 #ifdef TEST_FRAMEWORK
396                 ast_mutex_lock(&cache->lock);
397                 cache->cache_completed = 1;
398                 ast_cond_signal(&cache->cond);
399                 ast_mutex_unlock(&cache->lock);
400 #endif
401                 return 0;
402         }
403
404         expiration = MAX(ast_tvdiff_ms(ast_tvadd(cached->created, ast_samp2tv(cache->object_lifetime_maximum, 1)), ast_tvnow()),
405                 1);
406
407         cache->expire_id = ast_sched_add(sched, expiration, expire_objects_from_cache, ao2_bump(cache));
408         if (cache->expire_id < 0) {
409                 ao2_ref(cache, -1);
410                 return -1;
411         }
412
413         return 0;
414 }
415
416 /*!
417  * \internal
418  * \brief Remove the oldest item from the cache.
419  *
420  * \pre cache->objects is write-locked
421  *
422  * \param cache The cache from which to remove the oldest object
423  *
424  * \retval 0 Success
425  * \retval non-zero Failure
426  */
427 static int remove_oldest_from_cache(struct sorcery_memory_cache *cache)
428 {
429         struct sorcery_memory_cached_object *heap_old_object;
430         struct sorcery_memory_cached_object *hash_old_object;
431
432         heap_old_object = ast_heap_pop(cache->object_heap);
433         if (!heap_old_object) {
434                 return -1;
435         }
436         hash_old_object = ao2_find(cache->objects, heap_old_object,
437                 OBJ_SEARCH_OBJECT | OBJ_UNLINK | OBJ_NOLOCK);
438
439         ast_assert(heap_old_object == hash_old_object);
440
441         ao2_ref(hash_old_object, -1);
442
443         schedule_cache_expiration(cache);
444
445         return 0;
446 }
447
448 /*!
449  * \internal
450  * \brief Add a new object to the cache.
451  *
452  * \pre cache->objects is write-locked
453  *
454  * \param cache The cache in which to add the new object
455  * \param cached_object The object to add to the cache
456  *
457  * \retval 0 Success
458  * \retval non-zero Failure
459  */
460 static int add_to_cache(struct sorcery_memory_cache *cache,
461                 struct sorcery_memory_cached_object *cached_object)
462 {
463         if (!ao2_link_flags(cache->objects, cached_object, OBJ_NOLOCK)) {
464                 return -1;
465         }
466
467         if (ast_heap_push(cache->object_heap, cached_object)) {
468                 ao2_find(cache->objects, cached_object,
469                         OBJ_SEARCH_OBJECT | OBJ_UNLINK | OBJ_NODATA | OBJ_NOLOCK);
470                 return -1;
471         }
472
473         if (cache->expire_id == -1) {
474                 schedule_cache_expiration(cache);
475         }
476
477         return 0;
478 }
479
480 /*!
481  * \internal
482  * \brief Callback function to cache an object in a memory cache
483  *
484  * \param sorcery The sorcery instance
485  * \param data The sorcery memory cache
486  * \param object The object to cache
487  *
488  * \retval 0 success
489  * \retval -1 failure
490  */
491 static int sorcery_memory_cache_create(const struct ast_sorcery *sorcery, void *data, void *object)
492 {
493         struct sorcery_memory_cache *cache = data;
494         struct sorcery_memory_cached_object *cached;
495
496         cached = ao2_alloc_options(sizeof(*cached), sorcery_memory_cached_object_destructor,
497                 AO2_ALLOC_OPT_LOCK_NOLOCK);
498         if (!cached) {
499                 return -1;
500         }
501         cached->object = ao2_bump(object);
502         cached->created = ast_tvnow();
503
504         /* As there is no guarantee that this won't be called by multiple threads wanting to cache
505          * the same object we remove any old ones, which turns this into a create/update function
506          * in reality. As well since there's no guarantee that the object in the cache is the same
507          * one here we remove any old objects using the object identifier.
508          */
509
510         ao2_wrlock(cache->objects);
511         remove_from_cache(cache, ast_sorcery_object_get_id(object), 1);
512         if (cache->maximum_objects && ao2_container_count(cache->objects) >= cache->maximum_objects) {
513                 if (remove_oldest_from_cache(cache)) {
514                         ast_log(LOG_ERROR, "Unable to make room in cache for sorcery object '%s'.\n",
515                                 ast_sorcery_object_get_id(object));
516                         ao2_ref(cached, -1);
517                         ao2_unlock(cache->objects);
518                         return -1;
519                 }
520         }
521         if (add_to_cache(cache, cached)) {
522                 ast_log(LOG_ERROR, "Unable to add object '%s' to the cache\n",
523                         ast_sorcery_object_get_id(object));
524                 ao2_ref(cached, -1);
525                 ao2_unlock(cache->objects);
526                 return -1;
527         }
528         ao2_unlock(cache->objects);
529
530         ao2_ref(cached, -1);
531         return 0;
532 }
533
534 /*!
535  * \internal
536  * \brief Callback function to retrieve an object from a memory cache
537  *
538  * \param sorcery The sorcery instance
539  * \param data The sorcery memory cache
540  * \param type The type of the object to retrieve
541  * \param id The id of the object to retrieve
542  *
543  * \retval non-NULL success
544  * \retval NULL failure
545  */
546 static void *sorcery_memory_cache_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id)
547 {
548         struct sorcery_memory_cache *cache = data;
549         struct sorcery_memory_cached_object *cached;
550         void *object;
551
552         cached = ao2_find(cache->objects, id, OBJ_SEARCH_KEY);
553         if (!cached) {
554                 return NULL;
555         }
556
557         object = ao2_bump(cached->object);
558         ao2_ref(cached, -1);
559
560         return object;
561 }
562
563 /*!
564  * \internal
565  * \brief Callback function to finish configuring the memory cache and to prefetch objects
566  *
567  * \param data The sorcery memory cache
568  * \param sorcery The sorcery instance
569  * \param type The type of object being loaded
570  */
571 static void sorcery_memory_cache_load(void *data, const struct ast_sorcery *sorcery, const char *type)
572 {
573         struct sorcery_memory_cache *cache = data;
574
575         /* If no name was explicitly specified generate one given the sorcery instance and object type */
576         if (ast_strlen_zero(cache->name)) {
577                 ast_asprintf(&cache->name, "%s/%s", ast_sorcery_get_module(sorcery), type);
578         }
579
580         ao2_link(caches, cache);
581         ast_debug(1, "Memory cache '%s' associated with sorcery instance '%p' of module '%s' with object type '%s'\n",
582                 cache->name, sorcery, ast_sorcery_get_module(sorcery), type);
583 }
584
585 /*!
586  * \internal
587  * \brief Callback function to expire objects from the memory cache on reload (if configured)
588  *
589  * \param data The sorcery memory cache
590  * \param sorcery The sorcery instance
591  * \param type The type of object being reloaded
592  */
593 static void sorcery_memory_cache_reload(void *data, const struct ast_sorcery *sorcery, const char *type)
594 {
595 }
596
597 /*!
598  * \internal
599  * \brief Function used to take an unsigned integer based configuration option and parse it
600  *
601  * \param value The string value of the configuration option
602  * \param result The unsigned integer to place the result in
603  *
604  * \retval 0 failure
605  * \retval 1 success
606  */
607 static int configuration_parse_unsigned_integer(const char *value, unsigned int *result)
608 {
609         if (ast_strlen_zero(value) || !strncmp(value, "-", 1)) {
610                 return 0;
611         }
612
613         return sscanf(value, "%30u", result);
614 }
615
616 static int age_cmp(void *a, void *b)
617 {
618         return ast_tvcmp(((struct sorcery_memory_cached_object *) b)->created,
619                         ((struct sorcery_memory_cached_object *) a)->created);
620 }
621
622 /*!
623  * \internal
624  * \brief Callback function to create a new sorcery memory cache using provided configuration
625  *
626  * \param data A stringified configuration for the memory cache
627  *
628  * \retval non-NULL success
629  * \retval NULL failure
630  */
631 static void *sorcery_memory_cache_open(const char *data)
632 {
633         char *options = ast_strdup(data), *option;
634         RAII_VAR(struct sorcery_memory_cache *, cache, NULL, ao2_cleanup);
635
636         cache = ao2_alloc_options(sizeof(*cache), sorcery_memory_cache_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
637         if (!cache) {
638                 return NULL;
639         }
640
641         cache->expire_id = -1;
642
643         /* If no configuration options have been provided this memory cache will operate in a default
644          * configuration.
645          */
646         while (!ast_strlen_zero(options) && (option = strsep(&options, ","))) {
647                 char *name = strsep(&option, "="), *value = option;
648
649                 if (!strcasecmp(name, "name")) {
650                         if (ast_strlen_zero(value)) {
651                                 ast_log(LOG_ERROR, "A name must be specified for the memory cache\n");
652                                 return NULL;
653                         }
654                         ast_free(cache->name);
655                         cache->name = ast_strdup(value);
656                 } else if (!strcasecmp(name, "maximum_objects")) {
657                         if (configuration_parse_unsigned_integer(value, &cache->maximum_objects) != 1) {
658                                 ast_log(LOG_ERROR, "Unsupported maximum objects value of '%s' used for memory cache\n",
659                                         value);
660                                 return NULL;
661                         }
662                 } else if (!strcasecmp(name, "object_lifetime_maximum")) {
663                         if (configuration_parse_unsigned_integer(value, &cache->object_lifetime_maximum) != 1) {
664                                 ast_log(LOG_ERROR, "Unsupported object maximum lifetime value of '%s' used for memory cache\n",
665                                         value);
666                                 return NULL;
667                         }
668                 } else if (!strcasecmp(name, "object_lifetime_stale")) {
669                         if (configuration_parse_unsigned_integer(value, &cache->object_lifetime_stale) != 1) {
670                                 ast_log(LOG_ERROR, "Unsupported object stale lifetime value of '%s' used for memory cache\n",
671                                         value);
672                                 return NULL;
673                         }
674                 } else if (!strcasecmp(name, "prefetch")) {
675                         cache->prefetch = ast_true(value);
676                 } else if (!strcasecmp(name, "expire_on_reload")) {
677                         cache->expire_on_reload = ast_true(value);
678                 } else {
679                         ast_log(LOG_ERROR, "Unsupported option '%s' used for memory cache\n", name);
680                         return NULL;
681                 }
682         }
683
684         cache->objects = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_RWLOCK,
685                 cache->maximum_objects ? cache->maximum_objects : CACHE_CONTAINER_BUCKET_SIZE,
686                 sorcery_memory_cached_object_hash, sorcery_memory_cached_object_cmp);
687         if (!cache->objects) {
688                 ast_log(LOG_ERROR, "Could not create a container to hold cached objects for memory cache\n");
689                 return NULL;
690         }
691
692         cache->object_heap = ast_heap_create(CACHE_HEAP_INIT_HEIGHT, age_cmp,
693                 offsetof(struct sorcery_memory_cached_object, __heap_index));
694         if (!cache->object_heap) {
695                 ast_log(LOG_ERROR, "Could not create heap to hold cached objects\n");
696                 return NULL;
697         }
698
699         /* The memory cache is not linked to the caches container until the load callback is invoked.
700          * Linking occurs there so an intelligent cache name can be constructed using the module of
701          * the sorcery instance and the specific object type if no cache name was specified as part
702          * of the configuration.
703          */
704
705         /* This is done as RAII_VAR will drop the reference */
706         return ao2_bump(cache);
707 }
708
709 /*!
710  * \internal
711  * \brief Callback function to delete an object from a memory cache
712  *
713  * \param sorcery The sorcery instance
714  * \param data The sorcery memory cache
715  * \param object The object to cache
716  *
717  * \retval 0 success
718  * \retval -1 failure
719  */
720 static int sorcery_memory_cache_delete(const struct ast_sorcery *sorcery, void *data, void *object)
721 {
722         struct sorcery_memory_cache *cache = data;
723         int res;
724
725         ao2_wrlock(cache->objects);
726         res = remove_from_cache(cache, ast_sorcery_object_get_id(object), 1);
727         ao2_unlock(cache->objects);
728
729         if (res) {
730                 ast_log(LOG_ERROR, "Unable to delete object '%s' from sorcery cache\n", ast_sorcery_object_get_id(object));
731         }
732
733         return res;
734 }
735
736 /*!
737  * \internal
738  * \brief Callback function to terminate a memory cache
739  *
740  * \param data The sorcery memory cache
741  */
742 static void sorcery_memory_cache_close(void *data)
743 {
744         struct sorcery_memory_cache *cache = data;
745
746         /* This can occur if a cache is created but never loaded */
747         if (!ast_strlen_zero(cache->name)) {
748                 ao2_unlink(caches, cache);
749         }
750
751         if (cache->object_lifetime_maximum) {
752                 /* If object lifetime support is enabled we need to explicitly drop all cached objects here
753                  * and stop the scheduled task. Failure to do so could potentially keep the cache around for
754                  * a prolonged period of time.
755                  */
756                 ao2_wrlock(cache->objects);
757                 ao2_callback(cache->objects, OBJ_UNLINK | OBJ_NOLOCK | OBJ_NODATA | OBJ_MULTIPLE,
758                         NULL, NULL);
759                 AST_SCHED_DEL_UNREF(sched, cache->expire_id, ao2_ref(cache, -1));
760                 ao2_unlock(cache->objects);
761         }
762
763         ao2_ref(cache, -1);
764 }
765
766 #ifdef TEST_FRAMEWORK
767
768 /*! \brief Dummy sorcery object */
769 struct test_sorcery_object {
770         SORCERY_OBJECT(details);
771 };
772
773 /*!
774  * \internal
775  * \brief Allocator for test object
776  *
777  * \param id The identifier for the object
778  *
779  * \retval non-NULL success
780  * \retval NULL failure
781  */
782 static void *test_sorcery_object_alloc(const char *id)
783 {
784         return ast_sorcery_generic_alloc(sizeof(struct test_sorcery_object), NULL);
785 }
786
787 /*!
788  * \internal
789  * \brief Allocator for test sorcery instance
790  *
791  * \retval non-NULL success
792  * \retval NULL failure
793  */
794 static struct ast_sorcery *alloc_and_initialize_sorcery(void)
795 {
796         struct ast_sorcery *sorcery;
797
798         if (!(sorcery = ast_sorcery_open())) {
799                 return NULL;
800         }
801
802         if ((ast_sorcery_apply_default(sorcery, "test", "memory", NULL) != AST_SORCERY_APPLY_SUCCESS) ||
803                 ast_sorcery_internal_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) {
804                 ast_sorcery_unref(sorcery);
805                 return NULL;
806         }
807
808         return sorcery;
809 }
810
811 AST_TEST_DEFINE(open_with_valid_options)
812 {
813         int res = AST_TEST_PASS;
814         struct sorcery_memory_cache *cache;
815
816         switch (cmd) {
817         case TEST_INIT:
818                 info->name = "open_with_valid_options";
819                 info->category = "/res/res_sorcery_memory_cache/";
820                 info->summary = "Attempt to create sorcery memory caches using valid options";
821                 info->description = "This test performs the following:\n"
822                         "\t* Creates a memory cache with default configuration\n"
823                         "\t* Creates a memory cache with a maximum object count of 10 and verifies it\n"
824                         "\t* Creates a memory cache with a maximum object lifetime of 60 and verifies it\n"
825                         "\t* Creates a memory cache with a stale object lifetime of 90 and verifies it\n";
826                 return AST_TEST_NOT_RUN;
827         case TEST_EXECUTE:
828                 break;
829         }
830
831         cache = sorcery_memory_cache_open("");
832         if (!cache) {
833                 ast_test_status_update(test, "Failed to create a sorcery memory cache using default configuration\n");
834                 res = AST_TEST_FAIL;
835         } else {
836                 sorcery_memory_cache_close(cache);
837         }
838
839         cache = sorcery_memory_cache_open("maximum_objects=10");
840         if (!cache) {
841                 ast_test_status_update(test, "Failed to create a sorcery memory cache with a maximum object count of 10\n");
842                 res = AST_TEST_FAIL;
843         } else {
844                 if (cache->maximum_objects != 10) {
845                         ast_test_status_update(test, "Created a sorcery memory cache with a maximum object count of 10 but it has '%u'\n",
846                                 cache->maximum_objects);
847                 }
848                 sorcery_memory_cache_close(cache);
849         }
850
851         cache = sorcery_memory_cache_open("object_lifetime_maximum=60");
852         if (!cache) {
853                 ast_test_status_update(test, "Failed to create a sorcery memory cache with a maximum object lifetime of 60\n");
854                 res = AST_TEST_FAIL;
855         } else {
856                 if (cache->object_lifetime_maximum != 60) {
857                         ast_test_status_update(test, "Created a sorcery memory cache with a maximum object lifetime of 60 but it has '%u'\n",
858                                 cache->object_lifetime_maximum);
859                 }
860                 sorcery_memory_cache_close(cache);
861         }
862
863         cache = sorcery_memory_cache_open("object_lifetime_stale=90");
864         if (!cache) {
865                 ast_test_status_update(test, "Failed to create a sorcery memory cache with a stale object lifetime of 90\n");
866                 res = AST_TEST_FAIL;
867         } else {
868                 if (cache->object_lifetime_stale != 90) {
869                         ast_test_status_update(test, "Created a sorcery memory cache with a stale object lifetime of 90 but it has '%u'\n",
870                                 cache->object_lifetime_stale);
871                 }
872                 sorcery_memory_cache_close(cache);
873         }
874
875
876         return res;
877 }
878
879 AST_TEST_DEFINE(open_with_invalid_options)
880 {
881         int res = AST_TEST_PASS;
882         struct sorcery_memory_cache *cache;
883
884         switch (cmd) {
885         case TEST_INIT:
886                 info->name = "open_with_invalid_options";
887                 info->category = "/res/res_sorcery_memory_cache/";
888                 info->summary = "Attempt to create sorcery memory caches using invalid options";
889                 info->description = "This test attempts to perform the following:\n"
890                         "\t* Create a memory cache with an empty name\n"
891                         "\t* Create a memory cache with a maximum object count of -1\n"
892                         "\t* Create a memory cache with a maximum object count of toast\n"
893                         "\t* Create a memory cache with a maximum object lifetime of -1\n"
894                         "\t* Create a memory cache with a maximum object lifetime of toast\n"
895                         "\t* Create a memory cache with a stale object lifetime of -1\n"
896                         "\t* Create a memory cache with a stale object lifetime of toast\n";
897                 return AST_TEST_NOT_RUN;
898         case TEST_EXECUTE:
899                 break;
900         }
901
902         cache = sorcery_memory_cache_open("name=");
903         if (cache) {
904                 ast_test_status_update(test, "Created a sorcery memory cache with an empty name\n");
905                 sorcery_memory_cache_close(cache);
906                 res = AST_TEST_FAIL;
907         }
908
909         cache = sorcery_memory_cache_open("maximum_objects=-1");
910         if (cache) {
911                 ast_test_status_update(test, "Created a sorcery memory cache with a maximum object count of -1\n");
912                 sorcery_memory_cache_close(cache);
913                 res = AST_TEST_FAIL;
914         }
915
916         cache = sorcery_memory_cache_open("maximum_objects=toast");
917         if (cache) {
918                 ast_test_status_update(test, "Created a sorcery memory cache with a maximum object count of toast\n");
919                 sorcery_memory_cache_close(cache);
920                 res = AST_TEST_FAIL;
921         }
922
923         cache = sorcery_memory_cache_open("object_lifetime_maximum=-1");
924         if (cache) {
925                 ast_test_status_update(test, "Created a sorcery memory cache with an object lifetime maximum of -1\n");
926                 sorcery_memory_cache_close(cache);
927                 res = AST_TEST_FAIL;
928         }
929
930         cache = sorcery_memory_cache_open("object_lifetime_maximum=toast");
931         if (cache) {
932                 ast_test_status_update(test, "Created a sorcery memory cache with an object lifetime maximum of toast\n");
933                 sorcery_memory_cache_close(cache);
934                 res = AST_TEST_FAIL;
935         }
936
937         cache = sorcery_memory_cache_open("object_lifetime_stale=-1");
938         if (cache) {
939                 ast_test_status_update(test, "Created a sorcery memory cache with a stale object lifetime of -1\n");
940                 sorcery_memory_cache_close(cache);
941                 res = AST_TEST_FAIL;
942         }
943
944         cache = sorcery_memory_cache_open("object_lifetime_stale=toast");
945         if (cache) {
946                 ast_test_status_update(test, "Created a sorcery memory cache with a stale object lifetime of toast\n");
947                 sorcery_memory_cache_close(cache);
948                 res = AST_TEST_FAIL;
949         }
950
951         cache = sorcery_memory_cache_open("tacos");
952         if (cache) {
953                 ast_test_status_update(test, "Created a sorcery memory cache with an invalid configuration option 'tacos'\n");
954                 sorcery_memory_cache_close(cache);
955                 res = AST_TEST_FAIL;
956         }
957
958         return res;
959 }
960
961 AST_TEST_DEFINE(create_and_retrieve)
962 {
963         int res = AST_TEST_FAIL;
964         struct ast_sorcery *sorcery = NULL;
965         struct sorcery_memory_cache *cache = NULL;
966         RAII_VAR(void *, object, NULL, ao2_cleanup);
967         RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
968
969         switch (cmd) {
970         case TEST_INIT:
971                 info->name = "create";
972                 info->category = "/res/res_sorcery_memory_cache/";
973                 info->summary = "Attempt to create an object in the cache";
974                 info->description = "This test performs the following:\n"
975                         "\t* Creates a memory cache with default options\n"
976                         "\t* Creates a sorcery instance with a test object\n"
977                         "\t* Creates a test object with an id of test\n"
978                         "\t* Pushes the test object into the memory cache\n"
979                         "\t* Confirms that the test object is in the cache\n";
980                 return AST_TEST_NOT_RUN;
981         case TEST_EXECUTE:
982                 break;
983         }
984
985         cache = sorcery_memory_cache_open("");
986         if (!cache) {
987                 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
988                 goto cleanup;
989         }
990
991         if (ao2_container_count(cache->objects)) {
992                 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
993                 goto cleanup;
994         }
995
996         sorcery = alloc_and_initialize_sorcery();
997         if (!sorcery) {
998                 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
999                 goto cleanup;
1000         }
1001
1002         object = ast_sorcery_alloc(sorcery, "test", "test");
1003         if (!object) {
1004                 ast_test_status_update(test, "Failed to allocate a test object\n");
1005                 goto cleanup;
1006         }
1007
1008         sorcery_memory_cache_create(sorcery, cache, object);
1009
1010         if (!ao2_container_count(cache->objects)) {
1011                 ast_test_status_update(test, "Added test object to memory cache but cache remains empty\n");
1012                 goto cleanup;
1013         }
1014
1015         cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
1016         if (!cached_object) {
1017                 ast_test_status_update(test, "Object placed into memory cache could not be retrieved\n");
1018                 goto cleanup;
1019         }
1020
1021         if (cached_object != object) {
1022                 ast_test_status_update(test, "Object retrieved from memory cached is not the one we cached\n");
1023                 goto cleanup;
1024         }
1025
1026         res = AST_TEST_PASS;
1027
1028 cleanup:
1029         if (cache) {
1030                 sorcery_memory_cache_close(cache);
1031         }
1032         if (sorcery) {
1033                 ast_sorcery_unref(sorcery);
1034         }
1035
1036         return res;
1037 }
1038
1039 AST_TEST_DEFINE(update)
1040 {
1041         int res = AST_TEST_FAIL;
1042         struct ast_sorcery *sorcery = NULL;
1043         struct sorcery_memory_cache *cache = NULL;
1044         RAII_VAR(void *, original_object, NULL, ao2_cleanup);
1045         RAII_VAR(void *, updated_object, NULL, ao2_cleanup);
1046         RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
1047
1048         switch (cmd) {
1049         case TEST_INIT:
1050                 info->name = "create";
1051                 info->category = "/res/res_sorcery_memory_cache/";
1052                 info->summary = "Attempt to create and then update an object in the cache";
1053                 info->description = "This test performs the following:\n"
1054                         "\t* Creates a memory cache with default options\n"
1055                         "\t* Creates a sorcery instance with a test object\n"
1056                         "\t* Creates a test object with an id of test\n"
1057                         "\t* Pushes the test object into the memory cache\n"
1058                         "\t* Confirms that the test object is in the cache\n"
1059                         "\t* Creates a new test object with the same id of test\n"
1060                         "\t* Pushes the new test object into the memory cache\n"
1061                         "\t* Confirms that the new test object has replaced the old one\n";
1062                 return AST_TEST_NOT_RUN;
1063         case TEST_EXECUTE:
1064                 break;
1065         }
1066
1067         cache = sorcery_memory_cache_open("");
1068         if (!cache) {
1069                 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
1070                 goto cleanup;
1071         }
1072
1073         if (ao2_container_count(cache->objects)) {
1074                 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
1075                 goto cleanup;
1076         }
1077
1078         sorcery = alloc_and_initialize_sorcery();
1079         if (!sorcery) {
1080                 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
1081                 goto cleanup;
1082         }
1083
1084         original_object = ast_sorcery_alloc(sorcery, "test", "test");
1085         if (!original_object) {
1086                 ast_test_status_update(test, "Failed to allocate a test object\n");
1087                 goto cleanup;
1088         }
1089
1090         sorcery_memory_cache_create(sorcery, cache, original_object);
1091
1092         updated_object = ast_sorcery_alloc(sorcery, "test", "test");
1093         if (!updated_object) {
1094                 ast_test_status_update(test, "Failed to allocate an updated test object\n");
1095                 goto cleanup;
1096         }
1097
1098         sorcery_memory_cache_create(sorcery, cache, updated_object);
1099
1100         if (ao2_container_count(cache->objects) != 1) {
1101                 ast_test_status_update(test, "Added updated test object to memory cache but cache now contains %d objects instead of 1\n",
1102                         ao2_container_count(cache->objects));
1103                 goto cleanup;
1104         }
1105
1106         cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
1107         if (!cached_object) {
1108                 ast_test_status_update(test, "Updated object placed into memory cache could not be retrieved\n");
1109                 goto cleanup;
1110         }
1111
1112         if (cached_object == original_object) {
1113                 ast_test_status_update(test, "Updated object placed into memory cache but old one is being retrieved\n");
1114                 goto cleanup;
1115         } else if (cached_object != updated_object) {
1116                 ast_test_status_update(test, "Updated object placed into memory cache but different one is being retrieved\n");
1117                 goto cleanup;
1118         }
1119
1120         res = AST_TEST_PASS;
1121
1122 cleanup:
1123         if (cache) {
1124                 sorcery_memory_cache_close(cache);
1125         }
1126         if (sorcery) {
1127                 ast_sorcery_unref(sorcery);
1128         }
1129
1130         return res;
1131 }
1132
1133 AST_TEST_DEFINE(delete)
1134 {
1135         int res = AST_TEST_FAIL;
1136         struct ast_sorcery *sorcery = NULL;
1137         struct sorcery_memory_cache *cache = NULL;
1138         RAII_VAR(void *, object, NULL, ao2_cleanup);
1139         RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
1140
1141         switch (cmd) {
1142         case TEST_INIT:
1143                 info->name = "delete";
1144                 info->category = "/res/res_sorcery_memory_cache/";
1145                 info->summary = "Attempt to create and then delete an object in the cache";
1146                 info->description = "This test performs the following:\n"
1147                         "\t* Creates a memory cache with default options\n"
1148                         "\t* Creates a sorcery instance with a test object\n"
1149                         "\t* Creates a test object with an id of test\n"
1150                         "\t* Pushes the test object into the memory cache\n"
1151                         "\t* Confirms that the test object is in the cache\n"
1152                         "\t* Deletes the test object from the cache\n"
1153                         "\t* Confirms that the test object is no longer in the cache\n";
1154                 return AST_TEST_NOT_RUN;
1155         case TEST_EXECUTE:
1156                 break;
1157         }
1158
1159         cache = sorcery_memory_cache_open("");
1160         if (!cache) {
1161                 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
1162                 goto cleanup;
1163         }
1164
1165         if (ao2_container_count(cache->objects)) {
1166                 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
1167                 goto cleanup;
1168         }
1169
1170         sorcery = alloc_and_initialize_sorcery();
1171         if (!sorcery) {
1172                 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
1173                 goto cleanup;
1174         }
1175
1176         object = ast_sorcery_alloc(sorcery, "test", "test");
1177         if (!object) {
1178                 ast_test_status_update(test, "Failed to allocate a test object\n");
1179                 goto cleanup;
1180         }
1181
1182         sorcery_memory_cache_create(sorcery, cache, object);
1183
1184         if (!ao2_container_count(cache->objects)) {
1185                 ast_test_status_update(test, "Added test object to memory cache but cache contains no objects\n");
1186                 goto cleanup;
1187         }
1188
1189         cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
1190         if (!cached_object) {
1191                 ast_test_status_update(test, "Test object placed into memory cache could not be retrieved\n");
1192                 goto cleanup;
1193         }
1194
1195         ao2_ref(cached_object, -1);
1196         cached_object = NULL;
1197
1198         sorcery_memory_cache_delete(sorcery, cache, object);
1199
1200         cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
1201         if (cached_object) {
1202                 ast_test_status_update(test, "Test object deleted from memory cache can still be retrieved\n");
1203                 goto cleanup;
1204         }
1205
1206         res = AST_TEST_PASS;
1207
1208 cleanup:
1209         if (cache) {
1210                 sorcery_memory_cache_close(cache);
1211         }
1212         if (sorcery) {
1213                 ast_sorcery_unref(sorcery);
1214         }
1215
1216         return res;
1217 }
1218
1219 static int check_cache_content(struct ast_test *test, struct ast_sorcery *sorcery, struct sorcery_memory_cache *cache,
1220                 const char **in_cache, size_t num_in_cache, const char **not_in_cache, size_t num_not_in_cache)
1221 {
1222         int i;
1223         int res = 0;
1224         RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
1225
1226         for (i = 0; i < num_in_cache; ++i) {
1227                 cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", in_cache[i]);
1228                 if (!cached_object) {
1229                         ast_test_status_update(test, "Failed to retrieve '%s' object from the cache\n",
1230                                         in_cache[i]);
1231                         res = -1;
1232                 }
1233                 ao2_ref(cached_object, -1);
1234         }
1235
1236         for (i = 0; i < num_not_in_cache; ++i) {
1237                 cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", not_in_cache[i]);
1238                 if (cached_object) {
1239                         ast_test_status_update(test, "Retrieved '%s' object from the cache unexpectedly\n",
1240                                         not_in_cache[i]);
1241                         ao2_ref(cached_object, -1);
1242                         res = -1;
1243                 }
1244         }
1245
1246         return res;
1247 }
1248
1249 AST_TEST_DEFINE(maximum_objects)
1250 {
1251         int res = AST_TEST_FAIL;
1252         struct ast_sorcery *sorcery = NULL;
1253         struct sorcery_memory_cache *cache = NULL;
1254         RAII_VAR(void *, alice, NULL, ao2_cleanup);
1255         RAII_VAR(void *, bob, NULL, ao2_cleanup);
1256         RAII_VAR(void *, charlie, NULL, ao2_cleanup);
1257         RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
1258         const char *in_cache[2];
1259         const char *not_in_cache[2];
1260
1261         switch (cmd) {
1262         case TEST_INIT:
1263                 info->name = "maximum_objects";
1264                 info->category = "/res/res_sorcery_memory_cache/";
1265                 info->summary = "Ensure that the 'maximum_objects' option works as expected";
1266                 info->description = "This test performs the following:\n"
1267                         "\t* Creates a memory cache with maximum_objects=2\n"
1268                         "\t* Creates a sorcery instance\n"
1269                         "\t* Creates a three test objects: alice, bob, charlie, and david\n"
1270                         "\t* Pushes alice and bob into the memory cache\n"
1271                         "\t* Confirms that alice and bob are in the memory cache\n"
1272                         "\t* Pushes charlie into the memory cache\n"
1273                         "\t* Confirms that bob and charlie are in the memory cache\n"
1274                         "\t* Deletes charlie from the memory cache\n"
1275                         "\t* Confirms that only bob is in the memory cache\n"
1276                         "\t* Pushes alice into the memory cache\n"
1277                         "\t* Confirms that bob and alice are in the memory cache\n";
1278                 return AST_TEST_NOT_RUN;
1279         case TEST_EXECUTE:
1280                 break;
1281         }
1282
1283         cache = sorcery_memory_cache_open("maximum_objects=2");
1284         if (!cache) {
1285                 ast_test_status_update(test, "Failed to create a sorcery memory cache with maximum_objects=2\n");
1286                 goto cleanup;
1287         }
1288
1289         if (ao2_container_count(cache->objects)) {
1290                 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
1291                 goto cleanup;
1292         }
1293
1294         sorcery = alloc_and_initialize_sorcery();
1295         if (!sorcery) {
1296                 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
1297                 goto cleanup;
1298         }
1299
1300         alice = ast_sorcery_alloc(sorcery, "test", "alice");
1301         bob = ast_sorcery_alloc(sorcery, "test", "bob");
1302         charlie = ast_sorcery_alloc(sorcery, "test", "charlie");
1303
1304         if (!alice || !bob || !charlie) {
1305                 ast_test_status_update(test, "Failed to allocate sorcery object(s)\n");
1306                 goto cleanup;
1307         }
1308
1309         sorcery_memory_cache_create(sorcery, cache, alice);
1310         in_cache[0] = "alice";
1311         in_cache[1] = NULL;
1312         not_in_cache[0] = "bob";
1313         not_in_cache[1] = "charlie";
1314         if (check_cache_content(test, sorcery, cache, in_cache, 1, not_in_cache, 2)) {
1315                 goto cleanup;
1316         }
1317
1318         /* Delays are added to ensure that we are not adding cache entries within the
1319          * same microsecond
1320          */
1321         usleep(1000);
1322
1323         sorcery_memory_cache_create(sorcery, cache, bob);
1324         in_cache[0] = "alice";
1325         in_cache[1] = "bob";
1326         not_in_cache[0] = "charlie";
1327         not_in_cache[1] = NULL;
1328         if (check_cache_content(test, sorcery, cache, in_cache, 2, not_in_cache, 1)) {
1329                 goto cleanup;
1330         }
1331
1332         usleep(1000);
1333
1334         sorcery_memory_cache_create(sorcery, cache, charlie);
1335         in_cache[0] = "bob";
1336         in_cache[1] = "charlie";
1337         not_in_cache[0] = "alice";
1338         not_in_cache[1] = NULL;
1339         if (check_cache_content(test, sorcery, cache, in_cache, 2, not_in_cache, 1)) {
1340                 goto cleanup;
1341         }
1342         usleep(1000);
1343
1344         sorcery_memory_cache_delete(sorcery, cache, charlie);
1345         in_cache[0] = "bob";
1346         in_cache[1] = NULL;
1347         not_in_cache[0] = "alice";
1348         not_in_cache[1] = "charlie";
1349         if (check_cache_content(test, sorcery, cache, in_cache, 1, not_in_cache, 2)) {
1350                 goto cleanup;
1351         }
1352         usleep(1000);
1353
1354         sorcery_memory_cache_create(sorcery, cache, alice);
1355         in_cache[0] = "bob";
1356         in_cache[1] = "alice";
1357         not_in_cache[0] = "charlie";
1358         not_in_cache[1] = NULL;
1359         if (check_cache_content(test, sorcery, cache, in_cache, 2, not_in_cache, 1)) {
1360                 goto cleanup;
1361         }
1362
1363         res = AST_TEST_PASS;
1364
1365 cleanup:
1366         if (cache) {
1367                 sorcery_memory_cache_close(cache);
1368         }
1369         if (sorcery) {
1370                 ast_sorcery_unref(sorcery);
1371         }
1372
1373         return res;
1374 }
1375
1376 AST_TEST_DEFINE(expiration)
1377 {
1378         int res = AST_TEST_FAIL;
1379         struct ast_sorcery *sorcery = NULL;
1380         struct sorcery_memory_cache *cache = NULL;
1381         int i;
1382
1383         switch (cmd) {
1384         case TEST_INIT:
1385                 info->name = "expiration";
1386                 info->category = "/res/res_sorcery_memory_cache/";
1387                 info->summary = "Add objects to a cache configured with maximum lifetime, confirm they are removed";
1388                 info->description = "This test performs the following:\n"
1389                         "\t* Creates a memory cache with a maximum object lifetime of 5 seconds\n"
1390                         "\t* Pushes 10 objects into the memory cache\n"
1391                         "\t* Waits (up to) 10 seconds for expiration to occur\n"
1392                         "\t* Confirms that the objects have been removed from the cache\n";
1393                 return AST_TEST_NOT_RUN;
1394         case TEST_EXECUTE:
1395                 break;
1396         }
1397
1398         cache = sorcery_memory_cache_open("object_lifetime_maximum=5");
1399         if (!cache) {
1400                 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
1401                 goto cleanup;
1402         }
1403
1404         sorcery = alloc_and_initialize_sorcery();
1405         if (!sorcery) {
1406                 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
1407                 goto cleanup;
1408         }
1409
1410         cache->cache_notify = 1;
1411         ast_mutex_init(&cache->lock);
1412         ast_cond_init(&cache->cond, NULL);
1413
1414         for (i = 0; i < 5; ++i) {
1415                 char uuid[AST_UUID_STR_LEN];
1416                 void *object;
1417
1418                 object = ast_sorcery_alloc(sorcery, "test", ast_uuid_generate_str(uuid, sizeof(uuid)));
1419                 if (!object) {
1420                         ast_test_status_update(test, "Failed to allocate test object for expiration\n");
1421                         goto cleanup;
1422                 }
1423
1424                 sorcery_memory_cache_create(sorcery, cache, object);
1425
1426                 ao2_ref(object, -1);
1427         }
1428
1429         ast_mutex_lock(&cache->lock);
1430         while (!cache->cache_completed) {
1431                 struct timeval start = ast_tvnow();
1432                 struct timespec end = {
1433                         .tv_sec = start.tv_sec + 10,
1434                         .tv_nsec = start.tv_usec * 1000,
1435                 };
1436
1437                 if (ast_cond_timedwait(&cache->cond, &cache->lock, &end) == ETIMEDOUT) {
1438                         break;
1439                 }
1440         }
1441         ast_mutex_unlock(&cache->lock);
1442
1443         if (ao2_container_count(cache->objects)) {
1444                 ast_test_status_update(test, "Objects placed into the memory cache did not expire and get removed\n");
1445                 goto cleanup;
1446         }
1447
1448         res = AST_TEST_PASS;
1449
1450 cleanup:
1451         if (cache) {
1452                 if (cache->cache_notify) {
1453                         ast_cond_destroy(&cache->cond);
1454                         ast_mutex_destroy(&cache->lock);
1455                 }
1456                 sorcery_memory_cache_close(cache);
1457         }
1458         if (sorcery) {
1459                 ast_sorcery_unref(sorcery);
1460         }
1461
1462         return res;
1463 }
1464
1465 #endif
1466
1467 static int unload_module(void)
1468 {
1469         if (sched) {
1470                 ast_sched_context_destroy(sched);
1471                 sched = NULL;
1472         }
1473
1474         ao2_cleanup(caches);
1475
1476         ast_sorcery_wizard_unregister(&memory_cache_object_wizard);
1477
1478         AST_TEST_UNREGISTER(open_with_valid_options);
1479         AST_TEST_UNREGISTER(open_with_invalid_options);
1480         AST_TEST_UNREGISTER(create_and_retrieve);
1481         AST_TEST_UNREGISTER(update);
1482         AST_TEST_UNREGISTER(delete);
1483         AST_TEST_UNREGISTER(maximum_objects);
1484         AST_TEST_UNREGISTER(expiration);
1485
1486         return 0;
1487 }
1488
1489 static int load_module(void)
1490 {
1491         sched = ast_sched_context_create();
1492         if (!sched) {
1493                 ast_log(LOG_ERROR, "Failed to create scheduler for cache management\n");
1494                 unload_module();
1495                 return AST_MODULE_LOAD_DECLINE;
1496         }
1497
1498         if (ast_sched_start_thread(sched)) {
1499                 ast_log(LOG_ERROR, "Failed to create scheduler thread for cache management\n");
1500                 unload_module();
1501                 return AST_MODULE_LOAD_DECLINE;
1502         }
1503
1504         caches = ao2_container_alloc(CACHES_CONTAINER_BUCKET_SIZE, sorcery_memory_cache_hash,
1505                 sorcery_memory_cache_cmp);
1506         if (!caches) {
1507                 ast_log(LOG_ERROR, "Failed to create container for configured caches\n");
1508                 unload_module();
1509                 return AST_MODULE_LOAD_DECLINE;
1510         }
1511
1512         if (ast_sorcery_wizard_register(&memory_cache_object_wizard)) {
1513                 unload_module();
1514                 return AST_MODULE_LOAD_DECLINE;
1515         }
1516
1517         AST_TEST_REGISTER(open_with_valid_options);
1518         AST_TEST_REGISTER(open_with_invalid_options);
1519         AST_TEST_REGISTER(create_and_retrieve);
1520         AST_TEST_REGISTER(update);
1521         AST_TEST_REGISTER(delete);
1522         AST_TEST_REGISTER(maximum_objects);
1523         AST_TEST_REGISTER(expiration);
1524
1525         return AST_MODULE_LOAD_SUCCESS;
1526 }
1527
1528 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Sorcery Memory Cache Object Wizard",
1529         .support_level = AST_MODULE_SUPPORT_CORE,
1530         .load = load_module,
1531         .unload = unload_module,
1532         .load_pri = AST_MODPRI_REALTIME_DRIVER,
1533 );