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