Merge "test_sorcery_memory_cache_thrash: Add unit tests for thrashing the memory...
[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 #include "asterisk/cli.h"
42 #include "asterisk/manager.h"
43
44 /*** DOCUMENTATION
45         <manager name="SorceryMemoryCacheExpireObject" language="en_US">
46                 <synopsis>
47                         Expire (remove) an object from a sorcery memory cache.
48                 </synopsis>
49                 <syntax>
50                         <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
51                         <parameter name="Cache" required="true">
52                                 <para>The name of the cache to expire the object from.</para>
53                         </parameter>
54                         <parameter name="Object" required="true">
55                                 <para>The name of the object to expire.</para>
56                         </parameter>
57                 </syntax>
58                 <description>
59                         <para>Expires (removes) an object from a sorcery memory cache.</para>
60                 </description>
61         </manager>
62         <manager name="SorceryMemoryCacheExpire" language="en_US">
63                 <synopsis>
64                         Expire (remove) ALL objects from a sorcery memory cache.
65                 </synopsis>
66                 <syntax>
67                         <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
68                         <parameter name="Cache" required="true">
69                                 <para>The name of the cache to expire all objects from.</para>
70                         </parameter>
71                 </syntax>
72                 <description>
73                         <para>Expires (removes) ALL objects from a sorcery memory cache.</para>
74                 </description>
75         </manager>
76         <manager name="SorceryMemoryCacheStaleObject" language="en_US">
77                 <synopsis>
78                         Mark an object in a sorcery memory cache as stale.
79                 </synopsis>
80                 <syntax>
81                         <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
82                         <parameter name="Cache" required="true">
83                                 <para>The name of the cache to mark the object as stale in.</para>
84                         </parameter>
85                         <parameter name="Object" required="true">
86                                 <para>The name of the object to mark as stale.</para>
87                         </parameter>
88                 </syntax>
89                 <description>
90                         <para>Marks an object as stale within a sorcery memory cache.</para>
91                 </description>
92         </manager>
93         <manager name="SorceryMemoryCacheStale" language="en_US">
94                 <synopsis>
95                         Marks ALL objects in a sorcery memory cache as stale.
96                 </synopsis>
97                 <syntax>
98                         <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
99                         <parameter name="Cache" required="true">
100                                 <para>The name of the cache to mark all object as stale in.</para>
101                         </parameter>
102                 </syntax>
103                 <description>
104                         <para>Marks ALL objects in a sorcery memory cache as stale.</para>
105                 </description>
106         </manager>
107  ***/
108
109 /*! \brief Structure for storing a memory cache */
110 struct sorcery_memory_cache {
111         /*! \brief The name of the memory cache */
112         char *name;
113         /*! \brief Objects in the cache */
114         struct ao2_container *objects;
115         /*! \brief The maximum number of objects permitted in the cache, 0 if no limit */
116         unsigned int maximum_objects;
117         /*! \brief The maximum time (in seconds) an object will stay in the cache, 0 if no limit */
118         unsigned int object_lifetime_maximum;
119         /*! \brief The amount of time (in seconds) before an object is marked as stale, 0 if disabled */
120         unsigned int object_lifetime_stale;
121         /*! \brief Whether objects are prefetched from normal storage at load time, 0 if disabled */
122         unsigned int prefetch;
123         /** \brief Whether all objects are expired when the object type is reloaded, 0 if disabled */
124         unsigned int expire_on_reload;
125         /*! \brief Heap of cached objects. Oldest object is at the top. */
126         struct ast_heap *object_heap;
127         /*! \brief Scheduler item for expiring oldest object. */
128         int expire_id;
129 #ifdef TEST_FRAMEWORK
130         /*! \brief Variable used to indicate we should notify a test when we reach empty */
131         unsigned int cache_notify;
132         /*! \brief Mutex lock used for signaling when the cache has reached empty */
133         ast_mutex_t lock;
134         /*! \brief Condition used for signaling when the cache has reached empty */
135         ast_cond_t cond;
136         /*! \brief Variable that is set when the cache has reached empty */
137         unsigned int cache_completed;
138 #endif
139 };
140
141 /*! \brief Structure for stored a cached object */
142 struct sorcery_memory_cached_object {
143         /*! \brief The cached object */
144         void *object;
145         /*! \brief The time at which the object was created */
146         struct timeval created;
147         /*! \brief index required by heap */
148         ssize_t __heap_index;
149         /*! \brief scheduler id of stale update task */
150         int stale_update_sched_id;
151 };
152
153 static void *sorcery_memory_cache_open(const char *data);
154 static int sorcery_memory_cache_create(const struct ast_sorcery *sorcery, void *data, void *object);
155 static void sorcery_memory_cache_load(void *data, const struct ast_sorcery *sorcery, const char *type);
156 static void sorcery_memory_cache_reload(void *data, const struct ast_sorcery *sorcery, const char *type);
157 static void *sorcery_memory_cache_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type,
158         const char *id);
159 static int sorcery_memory_cache_delete(const struct ast_sorcery *sorcery, void *data, void *object);
160 static void sorcery_memory_cache_close(void *data);
161
162 static struct ast_sorcery_wizard memory_cache_object_wizard = {
163         .name = "memory_cache",
164         .open = sorcery_memory_cache_open,
165         .create = sorcery_memory_cache_create,
166         .update = sorcery_memory_cache_create,
167         .delete = sorcery_memory_cache_delete,
168         .load = sorcery_memory_cache_load,
169         .reload = sorcery_memory_cache_reload,
170         .retrieve_id = sorcery_memory_cache_retrieve_id,
171         .close = sorcery_memory_cache_close,
172 };
173
174 /*! \brief The bucket size for the container of caches */
175 #define CACHES_CONTAINER_BUCKET_SIZE 53
176
177 /*! \brief The default bucket size for the container of objects in the cache */
178 #define CACHE_CONTAINER_BUCKET_SIZE 53
179
180 /*! \brief Height of heap for cache object heap. Allows 31 initial objects */
181 #define CACHE_HEAP_INIT_HEIGHT 5
182
183 /*! \brief Container of created caches */
184 static struct ao2_container *caches;
185
186 /*! \brief Scheduler for cache management */
187 static struct ast_sched_context *sched;
188
189 #define STALE_UPDATE_THREAD_ID 0x5EED1E55
190 AST_THREADSTORAGE(stale_update_id_storage);
191
192 static int is_stale_update(void)
193 {
194         uint32_t *stale_update_thread_id;
195
196         stale_update_thread_id = ast_threadstorage_get(&stale_update_id_storage,
197                 sizeof(*stale_update_thread_id));
198         if (!stale_update_thread_id) {
199                 return 0;
200         }
201
202         return *stale_update_thread_id == STALE_UPDATE_THREAD_ID;
203 }
204
205 static void start_stale_update(void)
206 {
207         uint32_t *stale_update_thread_id;
208
209         stale_update_thread_id = ast_threadstorage_get(&stale_update_id_storage,
210                 sizeof(*stale_update_thread_id));
211         if (!stale_update_thread_id) {
212                 ast_log(LOG_ERROR, "Could not set stale update ID for sorcery memory cache thread\n");
213                 return;
214         }
215
216         *stale_update_thread_id = STALE_UPDATE_THREAD_ID;
217 }
218
219 static void end_stale_update(void)
220 {
221         uint32_t *stale_update_thread_id;
222
223         stale_update_thread_id = ast_threadstorage_get(&stale_update_id_storage,
224                 sizeof(*stale_update_thread_id));
225         if (!stale_update_thread_id) {
226                 ast_log(LOG_ERROR, "Could not set stale update ID for sorcery memory cache thread\n");
227                 return;
228         }
229
230         *stale_update_thread_id = 0;
231 }
232
233 /*!
234  * \internal
235  * \brief Hashing function for the container holding caches
236  *
237  * \param obj A sorcery memory cache or name of one
238  * \param flags Hashing flags
239  *
240  * \return The hash of the memory cache name
241  */
242 static int sorcery_memory_cache_hash(const void *obj, int flags)
243 {
244         const struct sorcery_memory_cache *cache = obj;
245         const char *name = obj;
246         int hash;
247
248         switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
249         default:
250         case OBJ_SEARCH_OBJECT:
251                 name = cache->name;
252                 /* Fall through */
253         case OBJ_SEARCH_KEY:
254                 hash = ast_str_hash(name);
255                 break;
256         case OBJ_SEARCH_PARTIAL_KEY:
257                 /* Should never happen in hash callback. */
258                 ast_assert(0);
259                 hash = 0;
260                 break;
261         }
262         return hash;
263 }
264
265 /*!
266  * \internal
267  * \brief Comparison function for the container holding caches
268  *
269  * \param obj A sorcery memory cache
270  * \param arg A sorcery memory cache, or name of one
271  * \param flags Comparison flags
272  *
273  * \retval CMP_MATCH if the name is the same
274  * \retval 0 if the name does not match
275  */
276 static int sorcery_memory_cache_cmp(void *obj, void *arg, int flags)
277 {
278         const struct sorcery_memory_cache *left = obj;
279         const struct sorcery_memory_cache *right = arg;
280         const char *right_name = arg;
281         int cmp;
282
283         switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
284         default:
285         case OBJ_SEARCH_OBJECT:
286                 right_name = right->name;
287                 /* Fall through */
288         case OBJ_SEARCH_KEY:
289                 cmp = strcmp(left->name, right_name);
290                 break;
291         case OBJ_SEARCH_PARTIAL_KEY:
292                 cmp = strncmp(left->name, right_name, strlen(right_name));
293                 break;
294         }
295         return cmp ? 0 : CMP_MATCH;
296 }
297
298 /*!
299  * \internal
300  * \brief Hashing function for the container holding cached objects
301  *
302  * \param obj A cached object or id of one
303  * \param flags Hashing flags
304  *
305  * \return The hash of the cached object id
306  */
307 static int sorcery_memory_cached_object_hash(const void *obj, int flags)
308 {
309         const struct sorcery_memory_cached_object *cached = obj;
310         const char *name = obj;
311         int hash;
312
313         switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
314         default:
315         case OBJ_SEARCH_OBJECT:
316                 name = ast_sorcery_object_get_id(cached->object);
317                 /* Fall through */
318         case OBJ_SEARCH_KEY:
319                 hash = ast_str_hash(name);
320                 break;
321         case OBJ_SEARCH_PARTIAL_KEY:
322                 /* Should never happen in hash callback. */
323                 ast_assert(0);
324                 hash = 0;
325                 break;
326         }
327         return hash;
328 }
329
330 /*!
331  * \internal
332  * \brief Comparison function for the container holding cached objects
333  *
334  * \param obj A cached object
335  * \param arg A cached object, or id of one
336  * \param flags Comparison flags
337  *
338  * \retval CMP_MATCH if the id is the same
339  * \retval 0 if the id does not match
340  */
341 static int sorcery_memory_cached_object_cmp(void *obj, void *arg, int flags)
342 {
343         struct sorcery_memory_cached_object *left = obj;
344         struct sorcery_memory_cached_object *right = arg;
345         const char *right_name = arg;
346         int cmp;
347
348         switch (flags & (OBJ_SEARCH_OBJECT | OBJ_SEARCH_KEY | OBJ_SEARCH_PARTIAL_KEY)) {
349         default:
350         case OBJ_SEARCH_OBJECT:
351                 right_name = ast_sorcery_object_get_id(right->object);
352                 /* Fall through */
353         case OBJ_SEARCH_KEY:
354                 cmp = strcmp(ast_sorcery_object_get_id(left->object), right_name);
355                 break;
356         case OBJ_SEARCH_PARTIAL_KEY:
357                 cmp = strncmp(ast_sorcery_object_get_id(left->object), right_name, strlen(right_name));
358                 break;
359         }
360         return cmp ? 0 : CMP_MATCH;
361 }
362
363 /*!
364  * \internal
365  * \brief Destructor function for a sorcery memory cache
366  *
367  * \param obj A sorcery memory cache
368  */
369 static void sorcery_memory_cache_destructor(void *obj)
370 {
371         struct sorcery_memory_cache *cache = obj;
372
373         ast_free(cache->name);
374         ao2_cleanup(cache->objects);
375         if (cache->object_heap) {
376                 ast_heap_destroy(cache->object_heap);
377         }
378 }
379
380 /*!
381  * \internal
382  * \brief Destructor function for sorcery memory cached objects
383  *
384  * \param obj A sorcery memory cached object
385  */
386 static void sorcery_memory_cached_object_destructor(void *obj)
387 {
388         struct sorcery_memory_cached_object *cached = obj;
389
390         ao2_cleanup(cached->object);
391 }
392
393 static int schedule_cache_expiration(struct sorcery_memory_cache *cache);
394
395 /*!
396  * \internal
397  * \brief Remove an object from the cache.
398  *
399  * This removes the item from both the hashtable and the heap.
400  *
401  * \pre cache->objects is write-locked
402  *
403  * \param cache The cache from which the object is being removed.
404  * \param id The sorcery object id of the object to remove.
405  * \param reschedule Reschedule cache expiration if this was the oldest object.
406  *
407  * \retval 0 Success
408  * \retval non-zero Failure
409  */
410 static int remove_from_cache(struct sorcery_memory_cache *cache, const char *id, int reschedule)
411 {
412         struct sorcery_memory_cached_object *hash_object;
413         struct sorcery_memory_cached_object *oldest_object;
414         struct sorcery_memory_cached_object *heap_object;
415
416         hash_object = ao2_find(cache->objects, id,
417                 OBJ_SEARCH_KEY | OBJ_UNLINK | OBJ_NOLOCK);
418         if (!hash_object) {
419                 return -1;
420         }
421
422         ast_assert(!strcmp(ast_sorcery_object_get_id(hash_object->object), id));
423
424         oldest_object = ast_heap_peek(cache->object_heap, 1);
425         heap_object = ast_heap_remove(cache->object_heap, hash_object);
426
427         ast_assert(heap_object == hash_object);
428
429         ao2_ref(hash_object, -1);
430
431         if (reschedule && (oldest_object == heap_object)) {
432                 schedule_cache_expiration(cache);
433         }
434
435         return 0;
436 }
437
438 /*!
439  * \internal
440  * \brief Scheduler callback invoked to expire old objects
441  *
442  * \param data The opaque callback data (in our case, the memory cache)
443  */
444 static int expire_objects_from_cache(const void *data)
445 {
446         struct sorcery_memory_cache *cache = (struct sorcery_memory_cache *)data;
447         struct sorcery_memory_cached_object *cached;
448
449         ao2_wrlock(cache->objects);
450
451         cache->expire_id = -1;
452
453         /* This is an optimization for objects which have been cached close to eachother */
454         while ((cached = ast_heap_peek(cache->object_heap, 1))) {
455                 int expiration;
456
457                 expiration = ast_tvdiff_ms(ast_tvadd(cached->created, ast_samp2tv(cache->object_lifetime_maximum, 1)), ast_tvnow());
458
459                 /* If the current oldest object has not yet expired stop and reschedule for it */
460                 if (expiration > 0) {
461                         break;
462                 }
463
464                 remove_from_cache(cache, ast_sorcery_object_get_id(cached->object), 0);
465         }
466
467         schedule_cache_expiration(cache);
468
469         ao2_unlock(cache->objects);
470
471         ao2_ref(cache, -1);
472
473         return 0;
474 }
475
476 /*!
477  * \internal
478  * \brief Remove all objects from the cache.
479  *
480  * This removes ALL objects from both the hash table and heap.
481  *
482  * \pre cache->objects is write-locked
483  *
484  * \param cache The cache to empty.
485  */
486 static void remove_all_from_cache(struct sorcery_memory_cache *cache)
487 {
488         while (ast_heap_pop(cache->object_heap));
489
490         ao2_callback(cache->objects, OBJ_UNLINK | OBJ_NOLOCK | OBJ_NODATA | OBJ_MULTIPLE,
491                 NULL, NULL);
492
493         AST_SCHED_DEL_UNREF(sched, cache->expire_id, ao2_ref(cache, -1));
494 }
495
496 /*!
497  * \internal
498  * \brief AO2 callback function for making an object stale immediately
499  *
500  * This changes the creation time of an object so it appears as though it is stale immediately.
501  *
502  * \param obj The cached object
503  * \param arg The cache itself
504  * \param flags Unused flags
505  */
506 static int object_stale_callback(void *obj, void *arg, int flags)
507 {
508         struct sorcery_memory_cached_object *cached = obj;
509         struct sorcery_memory_cache *cache = arg;
510
511         /* Since our granularity is seconds it's possible for something to retrieve us within a window
512          * where we wouldn't be treated as stale. To ensure that doesn't happen we use the configured stale
513          * time plus a second.
514          */
515         cached->created = ast_tvsub(cached->created, ast_samp2tv(cache->object_lifetime_stale + 1, 1));
516
517         return CMP_MATCH;
518 }
519
520 /*!
521  * \internal
522  * \brief Mark an object as stale explicitly.
523  *
524  * This changes the creation time of an object so it appears as though it is stale immediately.
525  *
526  * \pre cache->objects is read-locked
527  *
528  * \param cache The cache the object is in
529  * \param id The unique identifier of the object
530  *
531  * \retval 0 success
532  * \retval -1 failure
533  */
534 static int mark_object_as_stale_in_cache(struct sorcery_memory_cache *cache, const char *id)
535 {
536         struct sorcery_memory_cached_object *cached;
537
538         cached = ao2_find(cache->objects, id, OBJ_SEARCH_KEY | OBJ_NOLOCK);
539         if (!cached) {
540                 return -1;
541         }
542
543         ast_assert(!strcmp(ast_sorcery_object_get_id(cached->object), id));
544
545         object_stale_callback(cached, cache, 0);
546         ao2_ref(cached, -1);
547
548         return 0;
549 }
550
551 /*!
552  * \internal
553  * \brief Mark all objects as stale within a cache.
554  *
555  * This changes the creation time of ALL objects so they appear as though they are stale.
556  *
557  * \pre cache->objects is read-locked
558  *
559  * \param cache
560  */
561 static void mark_all_as_stale_in_cache(struct sorcery_memory_cache *cache)
562 {
563         ao2_callback(cache->objects, OBJ_NOLOCK | OBJ_NODATA | OBJ_MULTIPLE, object_stale_callback, cache);
564 }
565
566 /*!
567  * \internal
568  * \brief Schedule a callback for cached object expiration.
569  *
570  * \pre cache->objects is write-locked
571  *
572  * \param cache The cache that is having its callback scheduled.
573  *
574  * \retval 0 success
575  * \retval -1 failure
576  */
577 static int schedule_cache_expiration(struct sorcery_memory_cache *cache)
578 {
579         struct sorcery_memory_cached_object *cached;
580         int expiration = 0;
581
582         if (!cache->object_lifetime_maximum) {
583                 return 0;
584         }
585
586         if (cache->expire_id != -1) {
587                 /* If we can't unschedule this expiration then it is currently attempting to run,
588                  * so let it run - it just means that it'll be the one scheduling instead of us.
589                  */
590                 if (ast_sched_del(sched, cache->expire_id)) {
591                         return 0;
592                 }
593
594                 /* Since it successfully cancelled we need to drop the ref to the cache it had */
595                 ao2_ref(cache, -1);
596                 cache->expire_id = -1;
597         }
598
599         cached = ast_heap_peek(cache->object_heap, 1);
600         if (!cached) {
601 #ifdef TEST_FRAMEWORK
602                 ast_mutex_lock(&cache->lock);
603                 cache->cache_completed = 1;
604                 ast_cond_signal(&cache->cond);
605                 ast_mutex_unlock(&cache->lock);
606 #endif
607                 return 0;
608         }
609
610         expiration = MAX(ast_tvdiff_ms(ast_tvadd(cached->created, ast_samp2tv(cache->object_lifetime_maximum, 1)), ast_tvnow()),
611                 1);
612
613         cache->expire_id = ast_sched_add(sched, expiration, expire_objects_from_cache, ao2_bump(cache));
614         if (cache->expire_id < 0) {
615                 ao2_ref(cache, -1);
616                 return -1;
617         }
618
619         return 0;
620 }
621
622 /*!
623  * \internal
624  * \brief Remove the oldest item from the cache.
625  *
626  * \pre cache->objects is write-locked
627  *
628  * \param cache The cache from which to remove the oldest object
629  *
630  * \retval 0 Success
631  * \retval non-zero Failure
632  */
633 static int remove_oldest_from_cache(struct sorcery_memory_cache *cache)
634 {
635         struct sorcery_memory_cached_object *heap_old_object;
636         struct sorcery_memory_cached_object *hash_old_object;
637
638         heap_old_object = ast_heap_pop(cache->object_heap);
639         if (!heap_old_object) {
640                 return -1;
641         }
642         hash_old_object = ao2_find(cache->objects, heap_old_object,
643                 OBJ_SEARCH_OBJECT | OBJ_UNLINK | OBJ_NOLOCK);
644
645         ast_assert(heap_old_object == hash_old_object);
646
647         ao2_ref(hash_old_object, -1);
648
649         schedule_cache_expiration(cache);
650
651         return 0;
652 }
653
654 /*!
655  * \internal
656  * \brief Add a new object to the cache.
657  *
658  * \pre cache->objects is write-locked
659  *
660  * \param cache The cache in which to add the new object
661  * \param cached_object The object to add to the cache
662  *
663  * \retval 0 Success
664  * \retval non-zero Failure
665  */
666 static int add_to_cache(struct sorcery_memory_cache *cache,
667                 struct sorcery_memory_cached_object *cached_object)
668 {
669         if (!ao2_link_flags(cache->objects, cached_object, OBJ_NOLOCK)) {
670                 return -1;
671         }
672
673         if (ast_heap_push(cache->object_heap, cached_object)) {
674                 ao2_find(cache->objects, cached_object,
675                         OBJ_SEARCH_OBJECT | OBJ_UNLINK | OBJ_NODATA | OBJ_NOLOCK);
676                 return -1;
677         }
678
679         if (cache->expire_id == -1) {
680                 schedule_cache_expiration(cache);
681         }
682
683         return 0;
684 }
685
686 /*!
687  * \internal
688  * \brief Callback function to cache an object in a memory cache
689  *
690  * \param sorcery The sorcery instance
691  * \param data The sorcery memory cache
692  * \param object The object to cache
693  *
694  * \retval 0 success
695  * \retval -1 failure
696  */
697 static int sorcery_memory_cache_create(const struct ast_sorcery *sorcery, void *data, void *object)
698 {
699         struct sorcery_memory_cache *cache = data;
700         struct sorcery_memory_cached_object *cached;
701
702         cached = ao2_alloc(sizeof(*cached), sorcery_memory_cached_object_destructor);
703         if (!cached) {
704                 return -1;
705         }
706         cached->object = ao2_bump(object);
707         cached->created = ast_tvnow();
708         cached->stale_update_sched_id = -1;
709
710         /* As there is no guarantee that this won't be called by multiple threads wanting to cache
711          * the same object we remove any old ones, which turns this into a create/update function
712          * in reality. As well since there's no guarantee that the object in the cache is the same
713          * one here we remove any old objects using the object identifier.
714          */
715
716         ao2_wrlock(cache->objects);
717         remove_from_cache(cache, ast_sorcery_object_get_id(object), 1);
718         if (cache->maximum_objects && ao2_container_count(cache->objects) >= cache->maximum_objects) {
719                 if (remove_oldest_from_cache(cache)) {
720                         ast_log(LOG_ERROR, "Unable to make room in cache for sorcery object '%s'.\n",
721                                 ast_sorcery_object_get_id(object));
722                         ao2_ref(cached, -1);
723                         ao2_unlock(cache->objects);
724                         return -1;
725                 }
726                 ast_assert(ao2_container_count(cache->objects) != cache->maximum_objects);
727         }
728         if (add_to_cache(cache, cached)) {
729                 ast_log(LOG_ERROR, "Unable to add object '%s' to the cache\n",
730                         ast_sorcery_object_get_id(object));
731                 ao2_ref(cached, -1);
732                 ao2_unlock(cache->objects);
733                 return -1;
734         }
735         ao2_unlock(cache->objects);
736
737         ao2_ref(cached, -1);
738         return 0;
739 }
740
741 struct stale_update_task_data {
742         struct ast_sorcery *sorcery;
743         struct sorcery_memory_cache *cache;
744         void *object;
745 };
746
747 static void stale_update_task_data_destructor(void *obj)
748 {
749         struct stale_update_task_data *task_data = obj;
750
751         ao2_cleanup(task_data->cache);
752         ao2_cleanup(task_data->object);
753         ast_sorcery_unref(task_data->sorcery);
754 }
755
756 static struct stale_update_task_data *stale_update_task_data_alloc(struct ast_sorcery *sorcery,
757                 struct sorcery_memory_cache *cache, const char *type, void *object)
758 {
759         struct stale_update_task_data *task_data;
760
761         task_data = ao2_alloc_options(sizeof(*task_data), stale_update_task_data_destructor,
762                 AO2_ALLOC_OPT_LOCK_NOLOCK);
763         if (!task_data) {
764                 return NULL;
765         }
766
767         task_data->sorcery = ao2_bump(sorcery);
768         task_data->cache = ao2_bump(cache);
769         task_data->object = ao2_bump(object);
770
771         return task_data;
772 }
773
774 static int stale_item_update(const void *data)
775 {
776         struct stale_update_task_data *task_data = (struct stale_update_task_data *) data;
777         void *object;
778
779         start_stale_update();
780
781         object = ast_sorcery_retrieve_by_id(task_data->sorcery,
782                 ast_sorcery_object_get_type(task_data->object),
783                 ast_sorcery_object_get_id(task_data->object));
784         if (!object) {
785                 ast_debug(1, "Backend no longer has object type '%s' ID '%s'. Removing from cache\n",
786                         ast_sorcery_object_get_type(task_data->object),
787                         ast_sorcery_object_get_id(task_data->object));
788                 sorcery_memory_cache_delete(task_data->sorcery, task_data->cache,
789                         task_data->object);
790         } else {
791                 ast_debug(1, "Refreshing stale cache object type '%s' ID '%s'\n",
792                         ast_sorcery_object_get_type(task_data->object),
793                         ast_sorcery_object_get_id(task_data->object));
794                 sorcery_memory_cache_create(task_data->sorcery, task_data->cache,
795                         object);
796         }
797
798         ast_test_suite_event_notify("SORCERY_MEMORY_CACHE_REFRESHED", "Cache: %s\r\nType: %s\r\nName: %s\r\n",
799                 task_data->cache->name, ast_sorcery_object_get_type(task_data->object),
800                 ast_sorcery_object_get_id(task_data->object));
801
802         ao2_ref(task_data, -1);
803         end_stale_update();
804
805         return 0;
806 }
807
808 /*!
809  * \internal
810  * \brief Callback function to retrieve an object from a memory cache
811  *
812  * \param sorcery The sorcery instance
813  * \param data The sorcery memory cache
814  * \param type The type of the object to retrieve
815  * \param id The id of the object to retrieve
816  *
817  * \retval non-NULL success
818  * \retval NULL failure
819  */
820 static void *sorcery_memory_cache_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id)
821 {
822         struct sorcery_memory_cache *cache = data;
823         struct sorcery_memory_cached_object *cached;
824         void *object;
825
826         if (is_stale_update()) {
827                 return NULL;
828         }
829
830         cached = ao2_find(cache->objects, id, OBJ_SEARCH_KEY);
831         if (!cached) {
832                 return NULL;
833         }
834
835         ast_assert(!strcmp(ast_sorcery_object_get_id(cached->object), id));
836
837         if (cache->object_lifetime_stale) {
838                 struct timeval elapsed;
839
840                 elapsed = ast_tvsub(ast_tvnow(), cached->created);
841                 if (elapsed.tv_sec > cache->object_lifetime_stale) {
842                         ao2_lock(cached);
843                         if (cached->stale_update_sched_id == -1) {
844                                 struct stale_update_task_data *task_data;
845
846                                 task_data = stale_update_task_data_alloc((struct ast_sorcery *)sorcery, cache,
847                                         type, cached->object);
848                                 if (task_data) {
849                                         ast_debug(1, "Cached sorcery object type '%s' ID '%s' is stale. Refreshing\n",
850                                                 type, id);
851                                         cached->stale_update_sched_id = ast_sched_add(sched, 1, stale_item_update, task_data);
852                                 } else {
853                                         ast_log(LOG_ERROR, "Unable to update stale cached object type '%s', ID '%s'.\n",
854                                                 type, id);
855                                 }
856                         }
857                         ao2_unlock(cached);
858                 }
859         }
860
861         object = ao2_bump(cached->object);
862         ao2_ref(cached, -1);
863
864         return object;
865 }
866
867 /*!
868  * \internal
869  * \brief Callback function to finish configuring the memory cache and to prefetch objects
870  *
871  * \param data The sorcery memory cache
872  * \param sorcery The sorcery instance
873  * \param type The type of object being loaded
874  */
875 static void sorcery_memory_cache_load(void *data, const struct ast_sorcery *sorcery, const char *type)
876 {
877         struct sorcery_memory_cache *cache = data;
878
879         /* If no name was explicitly specified generate one given the sorcery instance and object type */
880         if (ast_strlen_zero(cache->name)) {
881                 ast_asprintf(&cache->name, "%s/%s", ast_sorcery_get_module(sorcery), type);
882         }
883
884         ao2_link(caches, cache);
885         ast_debug(1, "Memory cache '%s' associated with sorcery instance '%p' of module '%s' with object type '%s'\n",
886                 cache->name, sorcery, ast_sorcery_get_module(sorcery), type);
887 }
888
889 /*!
890  * \internal
891  * \brief Callback function to expire objects from the memory cache on reload (if configured)
892  *
893  * \param data The sorcery memory cache
894  * \param sorcery The sorcery instance
895  * \param type The type of object being reloaded
896  */
897 static void sorcery_memory_cache_reload(void *data, const struct ast_sorcery *sorcery, const char *type)
898 {
899         struct sorcery_memory_cache *cache = data;
900
901         if (!cache->expire_on_reload) {
902                 return;
903         }
904
905         ao2_wrlock(cache->objects);
906         remove_all_from_cache(cache);
907         ao2_unlock(cache->objects);
908 }
909
910 /*!
911  * \internal
912  * \brief Function used to take an unsigned integer based configuration option and parse it
913  *
914  * \param value The string value of the configuration option
915  * \param result The unsigned integer to place the result in
916  *
917  * \retval 0 failure
918  * \retval 1 success
919  */
920 static int configuration_parse_unsigned_integer(const char *value, unsigned int *result)
921 {
922         if (ast_strlen_zero(value) || !strncmp(value, "-", 1)) {
923                 return 0;
924         }
925
926         return sscanf(value, "%30u", result);
927 }
928
929 static int age_cmp(void *a, void *b)
930 {
931         return ast_tvcmp(((struct sorcery_memory_cached_object *) b)->created,
932                         ((struct sorcery_memory_cached_object *) a)->created);
933 }
934
935 /*!
936  * \internal
937  * \brief Callback function to create a new sorcery memory cache using provided configuration
938  *
939  * \param data A stringified configuration for the memory cache
940  *
941  * \retval non-NULL success
942  * \retval NULL failure
943  */
944 static void *sorcery_memory_cache_open(const char *data)
945 {
946         char *options = ast_strdup(data), *option;
947         RAII_VAR(struct sorcery_memory_cache *, cache, NULL, ao2_cleanup);
948
949         cache = ao2_alloc_options(sizeof(*cache), sorcery_memory_cache_destructor, AO2_ALLOC_OPT_LOCK_NOLOCK);
950         if (!cache) {
951                 return NULL;
952         }
953
954         cache->expire_id = -1;
955
956         /* If no configuration options have been provided this memory cache will operate in a default
957          * configuration.
958          */
959         while (!ast_strlen_zero(options) && (option = strsep(&options, ","))) {
960                 char *name = strsep(&option, "="), *value = option;
961
962                 if (!strcasecmp(name, "name")) {
963                         if (ast_strlen_zero(value)) {
964                                 ast_log(LOG_ERROR, "A name must be specified for the memory cache\n");
965                                 return NULL;
966                         }
967                         ast_free(cache->name);
968                         cache->name = ast_strdup(value);
969                 } else if (!strcasecmp(name, "maximum_objects")) {
970                         if (configuration_parse_unsigned_integer(value, &cache->maximum_objects) != 1) {
971                                 ast_log(LOG_ERROR, "Unsupported maximum objects value of '%s' used for memory cache\n",
972                                         value);
973                                 return NULL;
974                         }
975                 } else if (!strcasecmp(name, "object_lifetime_maximum")) {
976                         if (configuration_parse_unsigned_integer(value, &cache->object_lifetime_maximum) != 1) {
977                                 ast_log(LOG_ERROR, "Unsupported object maximum lifetime value of '%s' used for memory cache\n",
978                                         value);
979                                 return NULL;
980                         }
981                 } else if (!strcasecmp(name, "object_lifetime_stale")) {
982                         if (configuration_parse_unsigned_integer(value, &cache->object_lifetime_stale) != 1) {
983                                 ast_log(LOG_ERROR, "Unsupported object stale lifetime value of '%s' used for memory cache\n",
984                                         value);
985                                 return NULL;
986                         }
987                 } else if (!strcasecmp(name, "prefetch")) {
988                         cache->prefetch = ast_true(value);
989                 } else if (!strcasecmp(name, "expire_on_reload")) {
990                         cache->expire_on_reload = ast_true(value);
991                 } else {
992                         ast_log(LOG_ERROR, "Unsupported option '%s' used for memory cache\n", name);
993                         return NULL;
994                 }
995         }
996
997         cache->objects = ao2_container_alloc_options(AO2_ALLOC_OPT_LOCK_RWLOCK,
998                 cache->maximum_objects ? cache->maximum_objects : CACHE_CONTAINER_BUCKET_SIZE,
999                 sorcery_memory_cached_object_hash, sorcery_memory_cached_object_cmp);
1000         if (!cache->objects) {
1001                 ast_log(LOG_ERROR, "Could not create a container to hold cached objects for memory cache\n");
1002                 return NULL;
1003         }
1004
1005         cache->object_heap = ast_heap_create(CACHE_HEAP_INIT_HEIGHT, age_cmp,
1006                 offsetof(struct sorcery_memory_cached_object, __heap_index));
1007         if (!cache->object_heap) {
1008                 ast_log(LOG_ERROR, "Could not create heap to hold cached objects\n");
1009                 return NULL;
1010         }
1011
1012         /* The memory cache is not linked to the caches container until the load callback is invoked.
1013          * Linking occurs there so an intelligent cache name can be constructed using the module of
1014          * the sorcery instance and the specific object type if no cache name was specified as part
1015          * of the configuration.
1016          */
1017
1018         /* This is done as RAII_VAR will drop the reference */
1019         return ao2_bump(cache);
1020 }
1021
1022 /*!
1023  * \internal
1024  * \brief Callback function to delete an object from a memory cache
1025  *
1026  * \param sorcery The sorcery instance
1027  * \param data The sorcery memory cache
1028  * \param object The object to cache
1029  *
1030  * \retval 0 success
1031  * \retval -1 failure
1032  */
1033 static int sorcery_memory_cache_delete(const struct ast_sorcery *sorcery, void *data, void *object)
1034 {
1035         struct sorcery_memory_cache *cache = data;
1036         int res;
1037
1038         ao2_wrlock(cache->objects);
1039         res = remove_from_cache(cache, ast_sorcery_object_get_id(object), 1);
1040         ao2_unlock(cache->objects);
1041
1042         if (res) {
1043                 ast_log(LOG_ERROR, "Unable to delete object '%s' from sorcery cache\n", ast_sorcery_object_get_id(object));
1044         }
1045
1046         return res;
1047 }
1048
1049 /*!
1050  * \internal
1051  * \brief Callback function to terminate a memory cache
1052  *
1053  * \param data The sorcery memory cache
1054  */
1055 static void sorcery_memory_cache_close(void *data)
1056 {
1057         struct sorcery_memory_cache *cache = data;
1058
1059         /* This can occur if a cache is created but never loaded */
1060         if (!ast_strlen_zero(cache->name)) {
1061                 ao2_unlink(caches, cache);
1062         }
1063
1064         if (cache->object_lifetime_maximum) {
1065                 /* If object lifetime support is enabled we need to explicitly drop all cached objects here
1066                  * and stop the scheduled task. Failure to do so could potentially keep the cache around for
1067                  * a prolonged period of time.
1068                  */
1069                 ao2_wrlock(cache->objects);
1070                 ao2_callback(cache->objects, OBJ_UNLINK | OBJ_NOLOCK | OBJ_NODATA | OBJ_MULTIPLE,
1071                         NULL, NULL);
1072                 AST_SCHED_DEL_UNREF(sched, cache->expire_id, ao2_ref(cache, -1));
1073                 ao2_unlock(cache->objects);
1074         }
1075
1076         ao2_ref(cache, -1);
1077 }
1078
1079 /*!
1080  * \internal
1081  * \brief CLI tab completion for cache names
1082  */
1083 static char *sorcery_memory_cache_complete_name(const char *word, int state)
1084 {
1085         struct sorcery_memory_cache *cache;
1086         struct ao2_iterator it_caches;
1087         int wordlen = strlen(word);
1088         int which = 0;
1089         char *result = NULL;
1090
1091         it_caches = ao2_iterator_init(caches, 0);
1092         while ((cache = ao2_iterator_next(&it_caches))) {
1093                 if (!strncasecmp(word, cache->name, wordlen)
1094                         && ++which > state) {
1095                         result = ast_strdup(cache->name);
1096                 }
1097                 ao2_ref(cache, -1);
1098                 if (result) {
1099                         break;
1100                 }
1101         }
1102         ao2_iterator_destroy(&it_caches);
1103         return result;
1104 }
1105
1106 /*!
1107  * \internal
1108  * \brief CLI command implementation for 'sorcery memory cache show'
1109  */
1110 static char *sorcery_memory_cache_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1111 {
1112         struct sorcery_memory_cache *cache;
1113
1114         switch (cmd) {
1115         case CLI_INIT:
1116                 e->command = "sorcery memory cache show";
1117                 e->usage =
1118                     "Usage: sorcery memory cache show <name>\n"
1119                     "       Show sorcery memory cache configuration and statistics.\n";
1120                 return NULL;
1121         case CLI_GENERATE:
1122                 if (a->pos == 4) {
1123                         return sorcery_memory_cache_complete_name(a->word, a->n);
1124                 } else {
1125                         return NULL;
1126                 }
1127         }
1128
1129         if (a->argc != 5) {
1130                 return CLI_SHOWUSAGE;
1131         }
1132
1133         cache = ao2_find(caches, a->argv[4], OBJ_SEARCH_KEY);
1134         if (!cache) {
1135                 ast_cli(a->fd, "Specified sorcery memory cache '%s' does not exist\n", a->argv[4]);
1136                 return CLI_FAILURE;
1137         }
1138
1139         ast_cli(a->fd, "Sorcery memory cache: %s\n", cache->name);
1140         ast_cli(a->fd, "Number of objects within cache: %d\n", ao2_container_count(cache->objects));
1141         if (cache->maximum_objects) {
1142                 ast_cli(a->fd, "Maximum allowed objects: %d\n", cache->maximum_objects);
1143         } else {
1144                 ast_cli(a->fd, "There is no limit on the maximum number of objects in the cache\n");
1145         }
1146         if (cache->object_lifetime_maximum) {
1147                 ast_cli(a->fd, "Number of seconds before object expires: %d\n", cache->object_lifetime_maximum);
1148         } else {
1149                 ast_cli(a->fd, "Object expiration is not enabled - cached objects will not expire\n");
1150         }
1151         if (cache->object_lifetime_stale) {
1152                 ast_cli(a->fd, "Number of seconds before object becomes stale: %d\n", cache->object_lifetime_stale);
1153         } else {
1154                 ast_cli(a->fd, "Object staleness is not enabled - cached objects will not go stale\n");
1155         }
1156         ast_cli(a->fd, "Prefetch: %s\n", AST_CLI_ONOFF(cache->prefetch));
1157         ast_cli(a->fd, "Expire all objects on reload: %s\n", AST_CLI_ONOFF(cache->expire_on_reload));
1158
1159         ao2_ref(cache, -1);
1160
1161         return CLI_SUCCESS;
1162 }
1163
1164 /*! \brief Structure used to pass data for printing cached object information */
1165 struct print_object_details {
1166         /*! \brief The sorcery memory cache */
1167         struct sorcery_memory_cache *cache;
1168         /*! \brief The CLI arguments */
1169         struct ast_cli_args *a;
1170 };
1171
1172 /*!
1173  * \internal
1174  * \brief Callback function for displaying object within the cache
1175  */
1176 static int sorcery_memory_cache_print_object(void *obj, void *arg, int flags)
1177 {
1178 #define FORMAT "%-25.25s %-15u %-15u \n"
1179         struct sorcery_memory_cached_object *cached = obj;
1180         struct print_object_details *details = arg;
1181         int seconds_until_expire = 0, seconds_until_stale = 0;
1182
1183         if (details->cache->object_lifetime_maximum) {
1184                 seconds_until_expire = ast_tvdiff_ms(ast_tvadd(cached->created, ast_samp2tv(details->cache->object_lifetime_maximum, 1)), ast_tvnow()) / 1000;
1185         }
1186         if (details->cache->object_lifetime_stale) {
1187                 seconds_until_stale = ast_tvdiff_ms(ast_tvadd(cached->created, ast_samp2tv(details->cache->object_lifetime_stale, 1)), ast_tvnow()) / 1000;
1188         }
1189
1190         ast_cli(details->a->fd, FORMAT, ast_sorcery_object_get_id(cached->object), MAX(seconds_until_stale, 0), MAX(seconds_until_expire, 0));
1191
1192         return CMP_MATCH;
1193 #undef FORMAT
1194 }
1195
1196 /*!
1197  * \internal
1198  * \brief CLI command implementation for 'sorcery memory cache dump'
1199  */
1200 static char *sorcery_memory_cache_dump(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1201 {
1202 #define FORMAT "%-25.25s %-15.15s %-15.15s \n"
1203         struct sorcery_memory_cache *cache;
1204         struct print_object_details details;
1205
1206         switch (cmd) {
1207         case CLI_INIT:
1208                 e->command = "sorcery memory cache dump";
1209                 e->usage =
1210                     "Usage: sorcery memory cache dump <name>\n"
1211                     "       Dump a list of the objects within the cache, listed by object identifier.\n";
1212                 return NULL;
1213         case CLI_GENERATE:
1214                 if (a->pos == 4) {
1215                         return sorcery_memory_cache_complete_name(a->word, a->n);
1216                 } else {
1217                         return NULL;
1218                 }
1219         }
1220
1221         if (a->argc != 5) {
1222                 return CLI_SHOWUSAGE;
1223         }
1224
1225         cache = ao2_find(caches, a->argv[4], OBJ_SEARCH_KEY);
1226         if (!cache) {
1227                 ast_cli(a->fd, "Specified sorcery memory cache '%s' does not exist\n", a->argv[4]);
1228                 return CLI_FAILURE;
1229         }
1230
1231         details.cache = cache;
1232         details.a = a;
1233
1234         ast_cli(a->fd, "Dumping sorcery memory cache '%s':\n", cache->name);
1235         if (!cache->object_lifetime_stale) {
1236                 ast_cli(a->fd, " * Staleness is not enabled - objects will not go stale\n");
1237         }
1238         if (!cache->object_lifetime_maximum) {
1239                 ast_cli(a->fd, " * Object lifetime is not enabled - objects will not expire\n");
1240         }
1241         ast_cli(a->fd, FORMAT, "Object Name", "Stale In", "Expires In");
1242         ast_cli(a->fd, FORMAT, "-------------------------", "---------------", "---------------");
1243         ao2_callback(cache->objects, OBJ_NODATA | OBJ_MULTIPLE, sorcery_memory_cache_print_object, &details);
1244         ast_cli(a->fd, FORMAT, "-------------------------", "---------------", "---------------");
1245         ast_cli(a->fd, "Total number of objects cached: %d\n", ao2_container_count(cache->objects));
1246
1247         ao2_ref(cache, -1);
1248
1249         return CLI_SUCCESS;
1250 #undef FORMAT
1251 }
1252
1253 /*!
1254  * \internal
1255  * \brief CLI tab completion for cached object names
1256  */
1257 static char *sorcery_memory_cache_complete_object_name(const char *cache_name, const char *word, int state)
1258 {
1259         struct sorcery_memory_cache *cache;
1260         struct sorcery_memory_cached_object *cached;
1261         struct ao2_iterator it_cached;
1262         int wordlen = strlen(word);
1263         int which = 0;
1264         char *result = NULL;
1265
1266         cache = ao2_find(caches, cache_name, OBJ_SEARCH_KEY);
1267         if (!cache) {
1268                 return NULL;
1269         }
1270
1271         it_cached = ao2_iterator_init(cache->objects, 0);
1272         while ((cached = ao2_iterator_next(&it_cached))) {
1273                 if (!strncasecmp(word, ast_sorcery_object_get_id(cached->object), wordlen)
1274                         && ++which > state) {
1275                         result = ast_strdup(ast_sorcery_object_get_id(cached->object));
1276                 }
1277                 ao2_ref(cached, -1);
1278                 if (result) {
1279                         break;
1280                 }
1281         }
1282         ao2_iterator_destroy(&it_cached);
1283
1284         ao2_ref(cache, -1);
1285
1286         return result;
1287 }
1288
1289 /*!
1290  * \internal
1291  * \brief CLI command implementation for 'sorcery memory cache expire'
1292  */
1293 static char *sorcery_memory_cache_expire(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1294 {
1295         struct sorcery_memory_cache *cache;
1296
1297         switch (cmd) {
1298         case CLI_INIT:
1299                 e->command = "sorcery memory cache expire";
1300                 e->usage =
1301                     "Usage: sorcery memory cache expire <cache name> [object name]\n"
1302                     "       Expire a specific object or ALL objects within a sorcery memory cache.\n";
1303                 return NULL;
1304         case CLI_GENERATE:
1305                 if (a->pos == 4) {
1306                         return sorcery_memory_cache_complete_name(a->word, a->n);
1307                 } else if (a->pos == 5) {
1308                         return sorcery_memory_cache_complete_object_name(a->argv[4], a->word, a->n);
1309                 } else {
1310                         return NULL;
1311                 }
1312         }
1313
1314         if (a->argc > 6) {
1315                 return CLI_SHOWUSAGE;
1316         }
1317
1318         cache = ao2_find(caches, a->argv[4], OBJ_SEARCH_KEY);
1319         if (!cache) {
1320                 ast_cli(a->fd, "Specified sorcery memory cache '%s' does not exist\n", a->argv[4]);
1321                 return CLI_FAILURE;
1322         }
1323
1324         ao2_wrlock(cache->objects);
1325         if (a->argc == 5) {
1326                 remove_all_from_cache(cache);
1327                 ast_cli(a->fd, "All objects have been removed from cache '%s'\n", a->argv[4]);
1328         } else {
1329                 if (!remove_from_cache(cache, a->argv[5], 1)) {
1330                         ast_cli(a->fd, "Successfully expired object '%s' from cache '%s'\n", a->argv[5], a->argv[4]);
1331                 } else {
1332                         ast_cli(a->fd, "Object '%s' was not expired from cache '%s' as it was not found\n", a->argv[5],
1333                                 a->argv[4]);
1334                 }
1335         }
1336         ao2_unlock(cache->objects);
1337
1338         ao2_ref(cache, -1);
1339
1340         return CLI_SUCCESS;
1341 }
1342
1343 /*!
1344  * \internal
1345  * \brief CLI command implementation for 'sorcery memory cache stale'
1346  */
1347 static char *sorcery_memory_cache_stale(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1348 {
1349         struct sorcery_memory_cache *cache;
1350
1351         switch (cmd) {
1352         case CLI_INIT:
1353                 e->command = "sorcery memory cache stale";
1354                 e->usage =
1355                     "Usage: sorcery memory cache stale <cache name> [object name]\n"
1356                     "       Mark a specific object or ALL objects as stale in a sorcery memory cache.\n";
1357                 return NULL;
1358         case CLI_GENERATE:
1359                 if (a->pos == 4) {
1360                         return sorcery_memory_cache_complete_name(a->word, a->n);
1361                 } else if (a->pos == 5) {
1362                         return sorcery_memory_cache_complete_object_name(a->argv[4], a->word, a->n);
1363                 } else {
1364                         return NULL;
1365                 }
1366         }
1367
1368         if (a->argc > 6) {
1369                 return CLI_SHOWUSAGE;
1370         }
1371
1372         cache = ao2_find(caches, a->argv[4], OBJ_SEARCH_KEY);
1373         if (!cache) {
1374                 ast_cli(a->fd, "Specified sorcery memory cache '%s' does not exist\n", a->argv[4]);
1375                 return CLI_FAILURE;
1376         }
1377
1378         if (!cache->object_lifetime_stale) {
1379                 ast_cli(a->fd, "Specified sorcery memory cache '%s' does not have staleness enabled\n", a->argv[4]);
1380                 ao2_ref(cache, -1);
1381                 return CLI_FAILURE;
1382         }
1383
1384         ao2_rdlock(cache->objects);
1385         if (a->argc == 5) {
1386                 mark_all_as_stale_in_cache(cache);
1387                 ast_cli(a->fd, "Marked all objects in sorcery memory cache '%s' as stale\n", a->argv[4]);
1388         } else {
1389                 if (!mark_object_as_stale_in_cache(cache, a->argv[5])) {
1390                         ast_cli(a->fd, "Successfully marked object '%s' in memory cache '%s' as stale\n",
1391                                 a->argv[5], a->argv[4]);
1392                 } else {
1393                         ast_cli(a->fd, "Object '%s' in sorcery memory cache '%s' could not be marked as stale as it was not found\n",
1394                                 a->argv[5], a->argv[4]);
1395                 }
1396         }
1397         ao2_unlock(cache->objects);
1398
1399         ao2_ref(cache, -1);
1400
1401         return CLI_SUCCESS;
1402 }
1403
1404 static struct ast_cli_entry cli_memory_cache[] = {
1405         AST_CLI_DEFINE(sorcery_memory_cache_show, "Show sorcery memory cache information"),
1406         AST_CLI_DEFINE(sorcery_memory_cache_dump, "Dump all objects within a sorcery memory cache"),
1407         AST_CLI_DEFINE(sorcery_memory_cache_expire, "Expire a specific object or ALL objects within a sorcery memory cache"),
1408         AST_CLI_DEFINE(sorcery_memory_cache_stale, "Mark a specific object or ALL objects as stale within a sorcery memory cache"),
1409 };
1410
1411 /*!
1412  * \internal
1413  * \brief AMI command implementation for 'SorceryMemoryCacheExpireObject'
1414  */
1415 static int sorcery_memory_cache_ami_expire_object(struct mansession *s, const struct message *m)
1416 {
1417         const char *cache_name = astman_get_header(m, "Cache");
1418         const char *object_name = astman_get_header(m, "Object");
1419         struct sorcery_memory_cache *cache;
1420         int res;
1421
1422         if (ast_strlen_zero(cache_name)) {
1423                 astman_send_error(s, m, "SorceryMemoryCacheExpireObject requires that a cache name be provided.\n");
1424                 return 0;
1425         } else if (ast_strlen_zero(object_name)) {
1426                 astman_send_error(s, m, "SorceryMemoryCacheExpireObject requires that an object name be provided\n");
1427                 return 0;
1428         }
1429
1430         cache = ao2_find(caches, cache_name, OBJ_SEARCH_KEY);
1431         if (!cache) {
1432                 astman_send_error(s, m, "The provided cache does not exist\n");
1433                 return 0;
1434         }
1435
1436         ao2_wrlock(cache->objects);
1437         res = remove_from_cache(cache, object_name, 1);
1438         ao2_unlock(cache->objects);
1439
1440         ao2_ref(cache, -1);
1441
1442         if (!res) {
1443                 astman_send_ack(s, m, "The provided object was expired from the cache\n");
1444         } else {
1445                 astman_send_error(s, m, "The provided object could not be expired from the cache\n");
1446         }
1447
1448         return 0;
1449 }
1450
1451 /*!
1452  * \internal
1453  * \brief AMI command implementation for 'SorceryMemoryCacheExpire'
1454  */
1455 static int sorcery_memory_cache_ami_expire(struct mansession *s, const struct message *m)
1456 {
1457         const char *cache_name = astman_get_header(m, "Cache");
1458         struct sorcery_memory_cache *cache;
1459
1460         if (ast_strlen_zero(cache_name)) {
1461                 astman_send_error(s, m, "SorceryMemoryCacheExpire requires that a cache name be provided.\n");
1462                 return 0;
1463         }
1464
1465         cache = ao2_find(caches, cache_name, OBJ_SEARCH_KEY);
1466         if (!cache) {
1467                 astman_send_error(s, m, "The provided cache does not exist\n");
1468                 return 0;
1469         }
1470
1471         ao2_wrlock(cache->objects);
1472         remove_all_from_cache(cache);
1473         ao2_unlock(cache->objects);
1474
1475         ao2_ref(cache, -1);
1476
1477         astman_send_ack(s, m, "All objects were expired from the cache\n");
1478
1479         return 0;
1480 }
1481
1482 /*!
1483  * \internal
1484  * \brief AMI command implementation for 'SorceryMemoryCacheStaleObject'
1485  */
1486 static int sorcery_memory_cache_ami_stale_object(struct mansession *s, const struct message *m)
1487 {
1488         const char *cache_name = astman_get_header(m, "Cache");
1489         const char *object_name = astman_get_header(m, "Object");
1490         struct sorcery_memory_cache *cache;
1491         int res;
1492
1493         if (ast_strlen_zero(cache_name)) {
1494                 astman_send_error(s, m, "SorceryMemoryCacheStaleObject requires that a cache name be provided.\n");
1495                 return 0;
1496         } else if (ast_strlen_zero(object_name)) {
1497                 astman_send_error(s, m, "SorceryMemoryCacheStaleObject requires that an object name be provided\n");
1498                 return 0;
1499         }
1500
1501         cache = ao2_find(caches, cache_name, OBJ_SEARCH_KEY);
1502         if (!cache) {
1503                 astman_send_error(s, m, "The provided cache does not exist\n");
1504                 return 0;
1505         }
1506
1507         ao2_rdlock(cache->objects);
1508         res = mark_object_as_stale_in_cache(cache, object_name);
1509         ao2_unlock(cache->objects);
1510
1511         ao2_ref(cache, -1);
1512
1513         if (!res) {
1514                 astman_send_ack(s, m, "The provided object was marked as stale in the cache\n");
1515         } else {
1516                 astman_send_error(s, m, "The provided object could not be marked as stale in the cache\n");
1517         }
1518
1519         return 0;
1520 }
1521
1522 /*!
1523  * \internal
1524  * \brief AMI command implementation for 'SorceryMemoryCacheStale'
1525  */
1526 static int sorcery_memory_cache_ami_stale(struct mansession *s, const struct message *m)
1527 {
1528         const char *cache_name = astman_get_header(m, "Cache");
1529         struct sorcery_memory_cache *cache;
1530
1531         if (ast_strlen_zero(cache_name)) {
1532                 astman_send_error(s, m, "SorceryMemoryCacheStale requires that a cache name be provided.\n");
1533                 return 0;
1534         }
1535
1536         cache = ao2_find(caches, cache_name, OBJ_SEARCH_KEY);
1537         if (!cache) {
1538                 astman_send_error(s, m, "The provided cache does not exist\n");
1539                 return 0;
1540         }
1541
1542         ao2_rdlock(cache->objects);
1543         mark_all_as_stale_in_cache(cache);
1544         ao2_unlock(cache->objects);
1545
1546         ao2_ref(cache, -1);
1547
1548         astman_send_ack(s, m, "All objects were marked as stale in the cache\n");
1549
1550         return 0;
1551 }
1552
1553 #ifdef TEST_FRAMEWORK
1554
1555 /*! \brief Dummy sorcery object */
1556 struct test_sorcery_object {
1557         SORCERY_OBJECT(details);
1558 };
1559
1560 /*!
1561  * \internal
1562  * \brief Allocator for test object
1563  *
1564  * \param id The identifier for the object
1565  *
1566  * \retval non-NULL success
1567  * \retval NULL failure
1568  */
1569 static void *test_sorcery_object_alloc(const char *id)
1570 {
1571         return ast_sorcery_generic_alloc(sizeof(struct test_sorcery_object), NULL);
1572 }
1573
1574 /*!
1575  * \internal
1576  * \brief Allocator for test sorcery instance
1577  *
1578  * \retval non-NULL success
1579  * \retval NULL failure
1580  */
1581 static struct ast_sorcery *alloc_and_initialize_sorcery(void)
1582 {
1583         struct ast_sorcery *sorcery;
1584
1585         if (!(sorcery = ast_sorcery_open())) {
1586                 return NULL;
1587         }
1588
1589         if ((ast_sorcery_apply_default(sorcery, "test", "memory", NULL) != AST_SORCERY_APPLY_SUCCESS) ||
1590                 ast_sorcery_internal_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) {
1591                 ast_sorcery_unref(sorcery);
1592                 return NULL;
1593         }
1594
1595         return sorcery;
1596 }
1597
1598 AST_TEST_DEFINE(open_with_valid_options)
1599 {
1600         int res = AST_TEST_PASS;
1601         struct sorcery_memory_cache *cache;
1602
1603         switch (cmd) {
1604         case TEST_INIT:
1605                 info->name = "open_with_valid_options";
1606                 info->category = "/res/res_sorcery_memory_cache/";
1607                 info->summary = "Attempt to create sorcery memory caches using valid options";
1608                 info->description = "This test performs the following:\n"
1609                         "\t* Creates a memory cache with default configuration\n"
1610                         "\t* Creates a memory cache with a maximum object count of 10 and verifies it\n"
1611                         "\t* Creates a memory cache with a maximum object lifetime of 60 and verifies it\n"
1612                         "\t* Creates a memory cache with a stale object lifetime of 90 and verifies it\n";
1613                 return AST_TEST_NOT_RUN;
1614         case TEST_EXECUTE:
1615                 break;
1616         }
1617
1618         cache = sorcery_memory_cache_open("");
1619         if (!cache) {
1620                 ast_test_status_update(test, "Failed to create a sorcery memory cache using default configuration\n");
1621                 res = AST_TEST_FAIL;
1622         } else {
1623                 sorcery_memory_cache_close(cache);
1624         }
1625
1626         cache = sorcery_memory_cache_open("maximum_objects=10");
1627         if (!cache) {
1628                 ast_test_status_update(test, "Failed to create a sorcery memory cache with a maximum object count of 10\n");
1629                 res = AST_TEST_FAIL;
1630         } else {
1631                 if (cache->maximum_objects != 10) {
1632                         ast_test_status_update(test, "Created a sorcery memory cache with a maximum object count of 10 but it has '%u'\n",
1633                                 cache->maximum_objects);
1634                 }
1635                 sorcery_memory_cache_close(cache);
1636         }
1637
1638         cache = sorcery_memory_cache_open("object_lifetime_maximum=60");
1639         if (!cache) {
1640                 ast_test_status_update(test, "Failed to create a sorcery memory cache with a maximum object lifetime of 60\n");
1641                 res = AST_TEST_FAIL;
1642         } else {
1643                 if (cache->object_lifetime_maximum != 60) {
1644                         ast_test_status_update(test, "Created a sorcery memory cache with a maximum object lifetime of 60 but it has '%u'\n",
1645                                 cache->object_lifetime_maximum);
1646                 }
1647                 sorcery_memory_cache_close(cache);
1648         }
1649
1650         cache = sorcery_memory_cache_open("object_lifetime_stale=90");
1651         if (!cache) {
1652                 ast_test_status_update(test, "Failed to create a sorcery memory cache with a stale object lifetime of 90\n");
1653                 res = AST_TEST_FAIL;
1654         } else {
1655                 if (cache->object_lifetime_stale != 90) {
1656                         ast_test_status_update(test, "Created a sorcery memory cache with a stale object lifetime of 90 but it has '%u'\n",
1657                                 cache->object_lifetime_stale);
1658                 }
1659                 sorcery_memory_cache_close(cache);
1660         }
1661
1662
1663         return res;
1664 }
1665
1666 AST_TEST_DEFINE(open_with_invalid_options)
1667 {
1668         int res = AST_TEST_PASS;
1669         struct sorcery_memory_cache *cache;
1670
1671         switch (cmd) {
1672         case TEST_INIT:
1673                 info->name = "open_with_invalid_options";
1674                 info->category = "/res/res_sorcery_memory_cache/";
1675                 info->summary = "Attempt to create sorcery memory caches using invalid options";
1676                 info->description = "This test attempts to perform the following:\n"
1677                         "\t* Create a memory cache with an empty name\n"
1678                         "\t* Create a memory cache with a maximum object count of -1\n"
1679                         "\t* Create a memory cache with a maximum object count of toast\n"
1680                         "\t* Create a memory cache with a maximum object lifetime of -1\n"
1681                         "\t* Create a memory cache with a maximum object lifetime of toast\n"
1682                         "\t* Create a memory cache with a stale object lifetime of -1\n"
1683                         "\t* Create a memory cache with a stale object lifetime of toast\n";
1684                 return AST_TEST_NOT_RUN;
1685         case TEST_EXECUTE:
1686                 break;
1687         }
1688
1689         cache = sorcery_memory_cache_open("name=");
1690         if (cache) {
1691                 ast_test_status_update(test, "Created a sorcery memory cache with an empty name\n");
1692                 sorcery_memory_cache_close(cache);
1693                 res = AST_TEST_FAIL;
1694         }
1695
1696         cache = sorcery_memory_cache_open("maximum_objects=-1");
1697         if (cache) {
1698                 ast_test_status_update(test, "Created a sorcery memory cache with a maximum object count of -1\n");
1699                 sorcery_memory_cache_close(cache);
1700                 res = AST_TEST_FAIL;
1701         }
1702
1703         cache = sorcery_memory_cache_open("maximum_objects=toast");
1704         if (cache) {
1705                 ast_test_status_update(test, "Created a sorcery memory cache with a maximum object count of toast\n");
1706                 sorcery_memory_cache_close(cache);
1707                 res = AST_TEST_FAIL;
1708         }
1709
1710         cache = sorcery_memory_cache_open("object_lifetime_maximum=-1");
1711         if (cache) {
1712                 ast_test_status_update(test, "Created a sorcery memory cache with an object lifetime maximum of -1\n");
1713                 sorcery_memory_cache_close(cache);
1714                 res = AST_TEST_FAIL;
1715         }
1716
1717         cache = sorcery_memory_cache_open("object_lifetime_maximum=toast");
1718         if (cache) {
1719                 ast_test_status_update(test, "Created a sorcery memory cache with an object lifetime maximum of toast\n");
1720                 sorcery_memory_cache_close(cache);
1721                 res = AST_TEST_FAIL;
1722         }
1723
1724         cache = sorcery_memory_cache_open("object_lifetime_stale=-1");
1725         if (cache) {
1726                 ast_test_status_update(test, "Created a sorcery memory cache with a stale object lifetime of -1\n");
1727                 sorcery_memory_cache_close(cache);
1728                 res = AST_TEST_FAIL;
1729         }
1730
1731         cache = sorcery_memory_cache_open("object_lifetime_stale=toast");
1732         if (cache) {
1733                 ast_test_status_update(test, "Created a sorcery memory cache with a stale object lifetime of toast\n");
1734                 sorcery_memory_cache_close(cache);
1735                 res = AST_TEST_FAIL;
1736         }
1737
1738         cache = sorcery_memory_cache_open("tacos");
1739         if (cache) {
1740                 ast_test_status_update(test, "Created a sorcery memory cache with an invalid configuration option 'tacos'\n");
1741                 sorcery_memory_cache_close(cache);
1742                 res = AST_TEST_FAIL;
1743         }
1744
1745         return res;
1746 }
1747
1748 AST_TEST_DEFINE(create_and_retrieve)
1749 {
1750         int res = AST_TEST_FAIL;
1751         struct ast_sorcery *sorcery = NULL;
1752         struct sorcery_memory_cache *cache = NULL;
1753         RAII_VAR(void *, object, NULL, ao2_cleanup);
1754         RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
1755
1756         switch (cmd) {
1757         case TEST_INIT:
1758                 info->name = "create";
1759                 info->category = "/res/res_sorcery_memory_cache/";
1760                 info->summary = "Attempt to create an object in the cache";
1761                 info->description = "This test performs the following:\n"
1762                         "\t* Creates a memory cache with default options\n"
1763                         "\t* Creates a sorcery instance with a test object\n"
1764                         "\t* Creates a test object with an id of test\n"
1765                         "\t* Pushes the test object into the memory cache\n"
1766                         "\t* Confirms that the test object is in the cache\n";
1767                 return AST_TEST_NOT_RUN;
1768         case TEST_EXECUTE:
1769                 break;
1770         }
1771
1772         cache = sorcery_memory_cache_open("");
1773         if (!cache) {
1774                 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
1775                 goto cleanup;
1776         }
1777
1778         if (ao2_container_count(cache->objects)) {
1779                 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
1780                 goto cleanup;
1781         }
1782
1783         sorcery = alloc_and_initialize_sorcery();
1784         if (!sorcery) {
1785                 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
1786                 goto cleanup;
1787         }
1788
1789         object = ast_sorcery_alloc(sorcery, "test", "test");
1790         if (!object) {
1791                 ast_test_status_update(test, "Failed to allocate a test object\n");
1792                 goto cleanup;
1793         }
1794
1795         sorcery_memory_cache_create(sorcery, cache, object);
1796
1797         if (!ao2_container_count(cache->objects)) {
1798                 ast_test_status_update(test, "Added test object to memory cache but cache remains empty\n");
1799                 goto cleanup;
1800         }
1801
1802         cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
1803         if (!cached_object) {
1804                 ast_test_status_update(test, "Object placed into memory cache could not be retrieved\n");
1805                 goto cleanup;
1806         }
1807
1808         if (cached_object != object) {
1809                 ast_test_status_update(test, "Object retrieved from memory cached is not the one we cached\n");
1810                 goto cleanup;
1811         }
1812
1813         res = AST_TEST_PASS;
1814
1815 cleanup:
1816         if (cache) {
1817                 sorcery_memory_cache_close(cache);
1818         }
1819         if (sorcery) {
1820                 ast_sorcery_unref(sorcery);
1821         }
1822
1823         return res;
1824 }
1825
1826 AST_TEST_DEFINE(update)
1827 {
1828         int res = AST_TEST_FAIL;
1829         struct ast_sorcery *sorcery = NULL;
1830         struct sorcery_memory_cache *cache = NULL;
1831         RAII_VAR(void *, original_object, NULL, ao2_cleanup);
1832         RAII_VAR(void *, updated_object, NULL, ao2_cleanup);
1833         RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
1834
1835         switch (cmd) {
1836         case TEST_INIT:
1837                 info->name = "create";
1838                 info->category = "/res/res_sorcery_memory_cache/";
1839                 info->summary = "Attempt to create and then update an object in the cache";
1840                 info->description = "This test performs the following:\n"
1841                         "\t* Creates a memory cache with default options\n"
1842                         "\t* Creates a sorcery instance with a test object\n"
1843                         "\t* Creates a test object with an id of test\n"
1844                         "\t* Pushes the test object into the memory cache\n"
1845                         "\t* Confirms that the test object is in the cache\n"
1846                         "\t* Creates a new test object with the same id of test\n"
1847                         "\t* Pushes the new test object into the memory cache\n"
1848                         "\t* Confirms that the new test object has replaced the old one\n";
1849                 return AST_TEST_NOT_RUN;
1850         case TEST_EXECUTE:
1851                 break;
1852         }
1853
1854         cache = sorcery_memory_cache_open("");
1855         if (!cache) {
1856                 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
1857                 goto cleanup;
1858         }
1859
1860         if (ao2_container_count(cache->objects)) {
1861                 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
1862                 goto cleanup;
1863         }
1864
1865         sorcery = alloc_and_initialize_sorcery();
1866         if (!sorcery) {
1867                 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
1868                 goto cleanup;
1869         }
1870
1871         original_object = ast_sorcery_alloc(sorcery, "test", "test");
1872         if (!original_object) {
1873                 ast_test_status_update(test, "Failed to allocate a test object\n");
1874                 goto cleanup;
1875         }
1876
1877         sorcery_memory_cache_create(sorcery, cache, original_object);
1878
1879         updated_object = ast_sorcery_alloc(sorcery, "test", "test");
1880         if (!updated_object) {
1881                 ast_test_status_update(test, "Failed to allocate an updated test object\n");
1882                 goto cleanup;
1883         }
1884
1885         sorcery_memory_cache_create(sorcery, cache, updated_object);
1886
1887         if (ao2_container_count(cache->objects) != 1) {
1888                 ast_test_status_update(test, "Added updated test object to memory cache but cache now contains %d objects instead of 1\n",
1889                         ao2_container_count(cache->objects));
1890                 goto cleanup;
1891         }
1892
1893         cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
1894         if (!cached_object) {
1895                 ast_test_status_update(test, "Updated object placed into memory cache could not be retrieved\n");
1896                 goto cleanup;
1897         }
1898
1899         if (cached_object == original_object) {
1900                 ast_test_status_update(test, "Updated object placed into memory cache but old one is being retrieved\n");
1901                 goto cleanup;
1902         } else if (cached_object != updated_object) {
1903                 ast_test_status_update(test, "Updated object placed into memory cache but different one is being retrieved\n");
1904                 goto cleanup;
1905         }
1906
1907         res = AST_TEST_PASS;
1908
1909 cleanup:
1910         if (cache) {
1911                 sorcery_memory_cache_close(cache);
1912         }
1913         if (sorcery) {
1914                 ast_sorcery_unref(sorcery);
1915         }
1916
1917         return res;
1918 }
1919
1920 AST_TEST_DEFINE(delete)
1921 {
1922         int res = AST_TEST_FAIL;
1923         struct ast_sorcery *sorcery = NULL;
1924         struct sorcery_memory_cache *cache = NULL;
1925         RAII_VAR(void *, object, NULL, ao2_cleanup);
1926         RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
1927
1928         switch (cmd) {
1929         case TEST_INIT:
1930                 info->name = "delete";
1931                 info->category = "/res/res_sorcery_memory_cache/";
1932                 info->summary = "Attempt to create and then delete an object in the cache";
1933                 info->description = "This test performs the following:\n"
1934                         "\t* Creates a memory cache with default options\n"
1935                         "\t* Creates a sorcery instance with a test object\n"
1936                         "\t* Creates a test object with an id of test\n"
1937                         "\t* Pushes the test object into the memory cache\n"
1938                         "\t* Confirms that the test object is in the cache\n"
1939                         "\t* Deletes the test object from the cache\n"
1940                         "\t* Confirms that the test object is no longer in the cache\n";
1941                 return AST_TEST_NOT_RUN;
1942         case TEST_EXECUTE:
1943                 break;
1944         }
1945
1946         cache = sorcery_memory_cache_open("");
1947         if (!cache) {
1948                 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
1949                 goto cleanup;
1950         }
1951
1952         if (ao2_container_count(cache->objects)) {
1953                 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
1954                 goto cleanup;
1955         }
1956
1957         sorcery = alloc_and_initialize_sorcery();
1958         if (!sorcery) {
1959                 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
1960                 goto cleanup;
1961         }
1962
1963         object = ast_sorcery_alloc(sorcery, "test", "test");
1964         if (!object) {
1965                 ast_test_status_update(test, "Failed to allocate a test object\n");
1966                 goto cleanup;
1967         }
1968
1969         sorcery_memory_cache_create(sorcery, cache, object);
1970
1971         if (!ao2_container_count(cache->objects)) {
1972                 ast_test_status_update(test, "Added test object to memory cache but cache contains no objects\n");
1973                 goto cleanup;
1974         }
1975
1976         cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
1977         if (!cached_object) {
1978                 ast_test_status_update(test, "Test object placed into memory cache could not be retrieved\n");
1979                 goto cleanup;
1980         }
1981
1982         ao2_ref(cached_object, -1);
1983         cached_object = NULL;
1984
1985         sorcery_memory_cache_delete(sorcery, cache, object);
1986
1987         cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
1988         if (cached_object) {
1989                 ast_test_status_update(test, "Test object deleted from memory cache can still be retrieved\n");
1990                 goto cleanup;
1991         }
1992
1993         res = AST_TEST_PASS;
1994
1995 cleanup:
1996         if (cache) {
1997                 sorcery_memory_cache_close(cache);
1998         }
1999         if (sorcery) {
2000                 ast_sorcery_unref(sorcery);
2001         }
2002
2003         return res;
2004 }
2005
2006 static int check_cache_content(struct ast_test *test, struct ast_sorcery *sorcery, struct sorcery_memory_cache *cache,
2007                 const char **in_cache, size_t num_in_cache, const char **not_in_cache, size_t num_not_in_cache)
2008 {
2009         int i;
2010         int res = 0;
2011         RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
2012
2013         for (i = 0; i < num_in_cache; ++i) {
2014                 cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", in_cache[i]);
2015                 if (!cached_object) {
2016                         ast_test_status_update(test, "Failed to retrieve '%s' object from the cache\n",
2017                                         in_cache[i]);
2018                         res = -1;
2019                 }
2020                 ao2_ref(cached_object, -1);
2021         }
2022
2023         for (i = 0; i < num_not_in_cache; ++i) {
2024                 cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", not_in_cache[i]);
2025                 if (cached_object) {
2026                         ast_test_status_update(test, "Retrieved '%s' object from the cache unexpectedly\n",
2027                                         not_in_cache[i]);
2028                         ao2_ref(cached_object, -1);
2029                         res = -1;
2030                 }
2031         }
2032
2033         return res;
2034 }
2035
2036 AST_TEST_DEFINE(maximum_objects)
2037 {
2038         int res = AST_TEST_FAIL;
2039         struct ast_sorcery *sorcery = NULL;
2040         struct sorcery_memory_cache *cache = NULL;
2041         RAII_VAR(void *, alice, NULL, ao2_cleanup);
2042         RAII_VAR(void *, bob, NULL, ao2_cleanup);
2043         RAII_VAR(void *, charlie, NULL, ao2_cleanup);
2044         RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
2045         const char *in_cache[2];
2046         const char *not_in_cache[2];
2047
2048         switch (cmd) {
2049         case TEST_INIT:
2050                 info->name = "maximum_objects";
2051                 info->category = "/res/res_sorcery_memory_cache/";
2052                 info->summary = "Ensure that the 'maximum_objects' option works as expected";
2053                 info->description = "This test performs the following:\n"
2054                         "\t* Creates a memory cache with maximum_objects=2\n"
2055                         "\t* Creates a sorcery instance\n"
2056                         "\t* Creates a three test objects: alice, bob, charlie, and david\n"
2057                         "\t* Pushes alice and bob into the memory cache\n"
2058                         "\t* Confirms that alice and bob are in the memory cache\n"
2059                         "\t* Pushes charlie into the memory cache\n"
2060                         "\t* Confirms that bob and charlie are in the memory cache\n"
2061                         "\t* Deletes charlie from the memory cache\n"
2062                         "\t* Confirms that only bob is in the memory cache\n"
2063                         "\t* Pushes alice into the memory cache\n"
2064                         "\t* Confirms that bob and alice are in the memory cache\n";
2065                 return AST_TEST_NOT_RUN;
2066         case TEST_EXECUTE:
2067                 break;
2068         }
2069
2070         cache = sorcery_memory_cache_open("maximum_objects=2");
2071         if (!cache) {
2072                 ast_test_status_update(test, "Failed to create a sorcery memory cache with maximum_objects=2\n");
2073                 goto cleanup;
2074         }
2075
2076         if (ao2_container_count(cache->objects)) {
2077                 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
2078                 goto cleanup;
2079         }
2080
2081         sorcery = alloc_and_initialize_sorcery();
2082         if (!sorcery) {
2083                 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
2084                 goto cleanup;
2085         }
2086
2087         alice = ast_sorcery_alloc(sorcery, "test", "alice");
2088         bob = ast_sorcery_alloc(sorcery, "test", "bob");
2089         charlie = ast_sorcery_alloc(sorcery, "test", "charlie");
2090
2091         if (!alice || !bob || !charlie) {
2092                 ast_test_status_update(test, "Failed to allocate sorcery object(s)\n");
2093                 goto cleanup;
2094         }
2095
2096         sorcery_memory_cache_create(sorcery, cache, alice);
2097         in_cache[0] = "alice";
2098         in_cache[1] = NULL;
2099         not_in_cache[0] = "bob";
2100         not_in_cache[1] = "charlie";
2101         if (check_cache_content(test, sorcery, cache, in_cache, 1, not_in_cache, 2)) {
2102                 goto cleanup;
2103         }
2104
2105         /* Delays are added to ensure that we are not adding cache entries within the
2106          * same microsecond
2107          */
2108         usleep(1000);
2109
2110         sorcery_memory_cache_create(sorcery, cache, bob);
2111         in_cache[0] = "alice";
2112         in_cache[1] = "bob";
2113         not_in_cache[0] = "charlie";
2114         not_in_cache[1] = NULL;
2115         if (check_cache_content(test, sorcery, cache, in_cache, 2, not_in_cache, 1)) {
2116                 goto cleanup;
2117         }
2118
2119         usleep(1000);
2120
2121         sorcery_memory_cache_create(sorcery, cache, charlie);
2122         in_cache[0] = "bob";
2123         in_cache[1] = "charlie";
2124         not_in_cache[0] = "alice";
2125         not_in_cache[1] = NULL;
2126         if (check_cache_content(test, sorcery, cache, in_cache, 2, not_in_cache, 1)) {
2127                 goto cleanup;
2128         }
2129         usleep(1000);
2130
2131         sorcery_memory_cache_delete(sorcery, cache, charlie);
2132         in_cache[0] = "bob";
2133         in_cache[1] = NULL;
2134         not_in_cache[0] = "alice";
2135         not_in_cache[1] = "charlie";
2136         if (check_cache_content(test, sorcery, cache, in_cache, 1, not_in_cache, 2)) {
2137                 goto cleanup;
2138         }
2139         usleep(1000);
2140
2141         sorcery_memory_cache_create(sorcery, cache, alice);
2142         in_cache[0] = "bob";
2143         in_cache[1] = "alice";
2144         not_in_cache[0] = "charlie";
2145         not_in_cache[1] = NULL;
2146         if (check_cache_content(test, sorcery, cache, in_cache, 2, not_in_cache, 1)) {
2147                 goto cleanup;
2148         }
2149
2150         res = AST_TEST_PASS;
2151
2152 cleanup:
2153         if (cache) {
2154                 sorcery_memory_cache_close(cache);
2155         }
2156         if (sorcery) {
2157                 ast_sorcery_unref(sorcery);
2158         }
2159
2160         return res;
2161 }
2162
2163 AST_TEST_DEFINE(expiration)
2164 {
2165         int res = AST_TEST_FAIL;
2166         struct ast_sorcery *sorcery = NULL;
2167         struct sorcery_memory_cache *cache = NULL;
2168         int i;
2169
2170         switch (cmd) {
2171         case TEST_INIT:
2172                 info->name = "expiration";
2173                 info->category = "/res/res_sorcery_memory_cache/";
2174                 info->summary = "Add objects to a cache configured with maximum lifetime, confirm they are removed";
2175                 info->description = "This test performs the following:\n"
2176                         "\t* Creates a memory cache with a maximum object lifetime of 5 seconds\n"
2177                         "\t* Pushes 10 objects into the memory cache\n"
2178                         "\t* Waits (up to) 10 seconds for expiration to occur\n"
2179                         "\t* Confirms that the objects have been removed from the cache\n";
2180                 return AST_TEST_NOT_RUN;
2181         case TEST_EXECUTE:
2182                 break;
2183         }
2184
2185         cache = sorcery_memory_cache_open("object_lifetime_maximum=5");
2186         if (!cache) {
2187                 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
2188                 goto cleanup;
2189         }
2190
2191         sorcery = alloc_and_initialize_sorcery();
2192         if (!sorcery) {
2193                 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
2194                 goto cleanup;
2195         }
2196
2197         cache->cache_notify = 1;
2198         ast_mutex_init(&cache->lock);
2199         ast_cond_init(&cache->cond, NULL);
2200
2201         for (i = 0; i < 5; ++i) {
2202                 char uuid[AST_UUID_STR_LEN];
2203                 void *object;
2204
2205                 object = ast_sorcery_alloc(sorcery, "test", ast_uuid_generate_str(uuid, sizeof(uuid)));
2206                 if (!object) {
2207                         ast_test_status_update(test, "Failed to allocate test object for expiration\n");
2208                         goto cleanup;
2209                 }
2210
2211                 sorcery_memory_cache_create(sorcery, cache, object);
2212
2213                 ao2_ref(object, -1);
2214         }
2215
2216         ast_mutex_lock(&cache->lock);
2217         while (!cache->cache_completed) {
2218                 struct timeval start = ast_tvnow();
2219                 struct timespec end = {
2220                         .tv_sec = start.tv_sec + 10,
2221                         .tv_nsec = start.tv_usec * 1000,
2222                 };
2223
2224                 if (ast_cond_timedwait(&cache->cond, &cache->lock, &end) == ETIMEDOUT) {
2225                         break;
2226                 }
2227         }
2228         ast_mutex_unlock(&cache->lock);
2229
2230         if (ao2_container_count(cache->objects)) {
2231                 ast_test_status_update(test, "Objects placed into the memory cache did not expire and get removed\n");
2232                 goto cleanup;
2233         }
2234
2235         res = AST_TEST_PASS;
2236
2237 cleanup:
2238         if (cache) {
2239                 if (cache->cache_notify) {
2240                         ast_cond_destroy(&cache->cond);
2241                         ast_mutex_destroy(&cache->lock);
2242                 }
2243                 sorcery_memory_cache_close(cache);
2244         }
2245         if (sorcery) {
2246                 ast_sorcery_unref(sorcery);
2247         }
2248
2249         return res;
2250 }
2251
2252 /*!
2253  * \brief Backend data that the mock sorcery wizard uses to create objects
2254  */
2255 static struct backend_data {
2256         /*! An arbitrary data field */
2257         int salt;
2258         /*! Another arbitrary data field */
2259         int pepper;
2260         /*! Indicates whether the backend has data */
2261         int exists;
2262 } *real_backend_data;
2263
2264 /*!
2265  * \brief Sorcery object created based on backend data
2266  */
2267 struct test_data {
2268         SORCERY_OBJECT(details);
2269         /*! Mirrors the backend data's salt field */
2270         int salt;
2271         /*! Mirrors the backend data's pepper field */
2272         int pepper;
2273 };
2274
2275 /*!
2276  * \brief Allocation callback for test_data sorcery object
2277  */
2278 static void *test_data_alloc(const char *id) {
2279         return ast_sorcery_generic_alloc(sizeof(struct test_data), NULL);
2280 }
2281
2282 /*!
2283  * \brief Callback for retrieving sorcery object by ID
2284  *
2285  * The mock wizard uses the \ref real_backend_data in order to construct
2286  * objects. If the backend data is "nonexisent" then no object is returned.
2287  * Otherwise, an object is created that has the backend data's salt and
2288  * pepper values copied.
2289  *
2290  * \param sorcery The sorcery instance
2291  * \param data Unused
2292  * \param type The object type. Will always be "test".
2293  * \param id The object id. Will always be "test".
2294  *
2295  * \retval NULL Backend data does not exist
2296  * \retval non-NULL An object representing the backend data
2297  */
2298 static void *mock_retrieve_id(const struct ast_sorcery *sorcery, void *data,
2299                 const char *type, const char *id)
2300 {
2301         struct test_data *b_data;
2302
2303         if (!real_backend_data->exists) {
2304                 return NULL;
2305         }
2306
2307         b_data = ast_sorcery_alloc(sorcery, type, id);
2308         if (!b_data) {
2309                 return NULL;
2310         }
2311
2312         b_data->salt = real_backend_data->salt;
2313         b_data->pepper = real_backend_data->pepper;
2314         return b_data;
2315 }
2316
2317 /*!
2318  * \brief A mock sorcery wizard used for the stale test
2319  */
2320 static struct ast_sorcery_wizard mock_wizard = {
2321         .name = "mock",
2322         .retrieve_id = mock_retrieve_id,
2323 };
2324
2325 /*!
2326  * \brief Wait for the cache to be updated after a stale object is retrieved.
2327  *
2328  * Since the cache does not know what type of objects it is dealing with, and
2329  * since we do not have the internals of the cache, the only way to make this
2330  * determination is to continuously retrieve an object from the cache until
2331  * we retrieve a different object than we had previously retrieved.
2332  *
2333  * \param sorcery The sorcery instance
2334  * \param previous_object The object we had previously retrieved from the cache
2335  * \param[out] new_object The new object we retrieve from the cache
2336  *
2337  * \retval 0 Successfully retrieved a new object from the cache
2338  * \retval non-zero Failed to retrieve a new object from the cache
2339  */
2340 static int wait_for_cache_update(const struct ast_sorcery *sorcery,
2341                 void *previous_object, struct test_data **new_object)
2342 {
2343         struct timeval start = ast_tvnow();
2344
2345         while (ast_remaining_ms(start, 5000) > 0) {
2346                 void *object;
2347
2348                 object = ast_sorcery_retrieve_by_id(sorcery, "test", "test");
2349                 if (object != previous_object) {
2350                         *new_object = object;
2351                         return 0;
2352                 }
2353                 ao2_cleanup(object);
2354         }
2355
2356         return -1;
2357 }
2358
2359 AST_TEST_DEFINE(stale)
2360 {
2361         int res = AST_TEST_FAIL;
2362         struct ast_sorcery *sorcery = NULL;
2363         struct test_data *backend_object;
2364         struct backend_data iterations[] = {
2365                 { .salt = 1,      .pepper = 2,       .exists = 1 },
2366                 { .salt = 568729, .pepper = -234123, .exists = 1 },
2367                 { .salt = 0,      .pepper = 0,       .exists = 0 },
2368         };
2369         struct backend_data initial = {
2370                 .salt = 0,
2371                 .pepper = 0,
2372                 .exists = 1,
2373         };
2374         int i;
2375
2376         switch (cmd) {
2377         case TEST_INIT:
2378                 info->name = "stale";
2379                 info->category = "/res/res_sorcery_memory_cache/";
2380                 info->summary = "Ensure that stale objects are replaced with updated objects";
2381                 info->description = "This test performs the following:\n"
2382                         "\t* Create a sorcery instance with two wizards"
2383                         "\t\t* The first is a memory cache that marks items stale after 3 seconds\n"
2384                         "\t\t* The second is a mock of a back-end\n"
2385                         "\t* Pre-populates the cache by retrieving some initial data from the backend.\n"
2386                         "\t* Performs iterations of the following:\n"
2387                         "\t\t* Update backend data with new values\n"
2388                         "\t\t* Retrieve item from the cache\n"
2389                         "\t\t* Ensure the retrieved item does not have the new backend values\n"
2390                         "\t\t* Wait for cached object to become stale\n"
2391                         "\t\t* Retrieve the stale cached object\n"
2392                         "\t\t* Ensure that the stale object retrieved is the same as the fresh one from earlier\n"
2393                         "\t\t* Wait for the cache to update with new data\n"
2394                         "\t\t* Ensure that new data in the cache matches backend data\n";
2395                 return AST_TEST_NOT_RUN;
2396         case TEST_EXECUTE:
2397                 break;
2398         }
2399
2400         ast_sorcery_wizard_register(&mock_wizard);
2401
2402         sorcery = ast_sorcery_open();
2403         if (!sorcery) {
2404                 ast_test_status_update(test, "Failed to create sorcery instance\n");
2405                 goto cleanup;
2406         }
2407
2408         ast_sorcery_apply_wizard_mapping(sorcery, "test", "memory_cache",
2409                         "object_lifetime_stale=3", 1);
2410         ast_sorcery_apply_wizard_mapping(sorcery, "test", "mock", NULL, 0);
2411         ast_sorcery_internal_object_register(sorcery, "test", test_data_alloc, NULL, NULL);
2412
2413         /* Prepopulate the cache */
2414         real_backend_data = &initial;
2415
2416         backend_object = ast_sorcery_retrieve_by_id(sorcery, "test", "test");
2417         if (!backend_object) {
2418                 ast_test_status_update(test, "Unable to retrieve backend data and populate the cache\n");
2419                 goto cleanup;
2420         }
2421         ao2_ref(backend_object, -1);
2422
2423         for (i = 0; i < ARRAY_LEN(iterations); ++i) {
2424                 RAII_VAR(struct test_data *, cache_fresh, NULL, ao2_cleanup);
2425                 RAII_VAR(struct test_data *, cache_stale, NULL, ao2_cleanup);
2426                 RAII_VAR(struct test_data *, cache_new, NULL, ao2_cleanup);
2427
2428                 real_backend_data = &iterations[i];
2429
2430                 ast_test_status_update(test, "Begininning iteration %d\n", i);
2431
2432                 cache_fresh = ast_sorcery_retrieve_by_id(sorcery, "test", "test");
2433                 if (!cache_fresh) {
2434                         ast_test_status_update(test, "Unable to retrieve fresh cached object\n");
2435                         goto cleanup;
2436                 }
2437
2438                 if (cache_fresh->salt == iterations[i].salt || cache_fresh->pepper == iterations[i].pepper) {
2439                         ast_test_status_update(test, "Fresh cached object has unexpected values. Did we hit the backend?\n");
2440                         goto cleanup;
2441                 }
2442
2443                 sleep(5);
2444
2445                 cache_stale = ast_sorcery_retrieve_by_id(sorcery, "test", "test");
2446                 if (!cache_stale) {
2447                         ast_test_status_update(test, "Unable to retrieve stale cached object\n");
2448                         goto cleanup;
2449                 }
2450
2451                 if (cache_stale != cache_fresh) {
2452                         ast_test_status_update(test, "Stale cache hit retrieved different object than fresh cache hit\n");
2453                         goto cleanup;
2454                 }
2455
2456                 if (wait_for_cache_update(sorcery, cache_stale, &cache_new)) {
2457                         ast_test_status_update(test, "Cache was not updated\n");
2458                         goto cleanup;
2459                 }
2460
2461                 if (iterations[i].exists) {
2462                         if (!cache_new) {
2463                                 ast_test_status_update(test, "Failed to retrieve item from cache when there should be one present\n");
2464                                 goto cleanup;
2465                         } else if (cache_new->salt != iterations[i].salt ||
2466                                         cache_new->pepper != iterations[i].pepper) {
2467                                 ast_test_status_update(test, "New cached item has unexpected values\n");
2468                                 goto cleanup;
2469                         }
2470                 } else if (cache_new) {
2471                         ast_test_status_update(test, "Retrieved a cached item when there should not have been one present\n");
2472                         goto cleanup;
2473                 }
2474         }
2475
2476         res = AST_TEST_PASS;
2477
2478 cleanup:
2479         if (sorcery) {
2480                 ast_sorcery_unref(sorcery);
2481         }
2482         ast_sorcery_wizard_unregister(&mock_wizard);
2483         return res;
2484 }
2485
2486 #endif
2487
2488 static int unload_module(void)
2489 {
2490         if (sched) {
2491                 ast_sched_context_destroy(sched);
2492                 sched = NULL;
2493         }
2494
2495         ao2_cleanup(caches);
2496
2497         ast_sorcery_wizard_unregister(&memory_cache_object_wizard);
2498
2499         ast_cli_unregister_multiple(cli_memory_cache, ARRAY_LEN(cli_memory_cache));
2500
2501         ast_manager_unregister("SorceryMemoryCacheExpireObject");
2502         ast_manager_unregister("SorceryMemoryCacheExpire");
2503         ast_manager_unregister("SorceryMemoryCacheStaleObject");
2504         ast_manager_unregister("SorceryMemoryCacheStale");
2505
2506         AST_TEST_UNREGISTER(open_with_valid_options);
2507         AST_TEST_UNREGISTER(open_with_invalid_options);
2508         AST_TEST_UNREGISTER(create_and_retrieve);
2509         AST_TEST_UNREGISTER(update);
2510         AST_TEST_UNREGISTER(delete);
2511         AST_TEST_UNREGISTER(maximum_objects);
2512         AST_TEST_UNREGISTER(expiration);
2513         AST_TEST_UNREGISTER(stale);
2514
2515         return 0;
2516 }
2517
2518 static int load_module(void)
2519 {
2520         int res;
2521
2522         sched = ast_sched_context_create();
2523         if (!sched) {
2524                 ast_log(LOG_ERROR, "Failed to create scheduler for cache management\n");
2525                 unload_module();
2526                 return AST_MODULE_LOAD_DECLINE;
2527         }
2528
2529         if (ast_sched_start_thread(sched)) {
2530                 ast_log(LOG_ERROR, "Failed to create scheduler thread for cache management\n");
2531                 unload_module();
2532                 return AST_MODULE_LOAD_DECLINE;
2533         }
2534
2535         caches = ao2_container_alloc(CACHES_CONTAINER_BUCKET_SIZE, sorcery_memory_cache_hash,
2536                 sorcery_memory_cache_cmp);
2537         if (!caches) {
2538                 ast_log(LOG_ERROR, "Failed to create container for configured caches\n");
2539                 unload_module();
2540                 return AST_MODULE_LOAD_DECLINE;
2541         }
2542
2543         if (ast_sorcery_wizard_register(&memory_cache_object_wizard)) {
2544                 unload_module();
2545                 return AST_MODULE_LOAD_DECLINE;
2546         }
2547
2548         res = ast_cli_register_multiple(cli_memory_cache, ARRAY_LEN(cli_memory_cache));
2549         res |= ast_manager_register_xml("SorceryMemoryCacheExpireObject", EVENT_FLAG_SYSTEM, sorcery_memory_cache_ami_expire_object);
2550         res |= ast_manager_register_xml("SorceryMemoryCacheExpire", EVENT_FLAG_SYSTEM, sorcery_memory_cache_ami_expire);
2551         res |= ast_manager_register_xml("SorceryMemoryCacheStaleObject", EVENT_FLAG_SYSTEM, sorcery_memory_cache_ami_stale_object);
2552         res |= ast_manager_register_xml("SorceryMemoryCacheStale", EVENT_FLAG_SYSTEM, sorcery_memory_cache_ami_stale);
2553
2554         if (res) {
2555                 unload_module();
2556                 return AST_MODULE_LOAD_DECLINE;
2557         }
2558
2559         AST_TEST_REGISTER(open_with_valid_options);
2560         AST_TEST_REGISTER(open_with_invalid_options);
2561         AST_TEST_REGISTER(create_and_retrieve);
2562         AST_TEST_REGISTER(update);
2563         AST_TEST_REGISTER(delete);
2564         AST_TEST_REGISTER(maximum_objects);
2565         AST_TEST_REGISTER(expiration);
2566         AST_TEST_REGISTER(stale);
2567
2568         return AST_MODULE_LOAD_SUCCESS;
2569 }
2570
2571 AST_MODULE_INFO(ASTERISK_GPL_KEY, AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER, "Sorcery Memory Cache Object Wizard",
2572         .support_level = AST_MODULE_SUPPORT_CORE,
2573         .load = load_module,
2574         .unload = unload_module,
2575         .load_pri = AST_MODPRI_REALTIME_DRIVER,
2576 );