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