Asterisk - The Open Source Telephony Project GIT-master-8924258
All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Modules Pages
res_sorcery_memory_cache.c
Go to the documentation of this file.
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#include "asterisk/module.h"
34#include "asterisk/sorcery.h"
35#include "asterisk/astobj2.h"
36#include "asterisk/sched.h"
37#include "asterisk/test.h"
38#include "asterisk/heap.h"
39#include "asterisk/cli.h"
40#include "asterisk/manager.h"
41
42/*** DOCUMENTATION
43 <manager name="SorceryMemoryCacheExpireObject" language="en_US">
44 <since>
45 <version>13.5.0</version>
46 </since>
47 <synopsis>
48 Expire (remove) an object from a sorcery memory cache.
49 </synopsis>
50 <syntax>
51 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
52 <parameter name="Cache" required="true">
53 <para>The name of the cache to expire the object from.</para>
54 </parameter>
55 <parameter name="Object" required="true">
56 <para>The name of the object to expire.</para>
57 </parameter>
58 </syntax>
59 <description>
60 <para>Expires (removes) an object from a sorcery memory cache. If full backend caching is enabled
61 this action is not available and will fail. In this case the SorceryMemoryCachePopulate or
62 SorceryMemoryCacheExpire AMI actions must be used instead.</para>
63 </description>
64 </manager>
65 <manager name="SorceryMemoryCacheExpire" language="en_US">
66 <since>
67 <version>13.5.0</version>
68 </since>
69 <synopsis>
70 Expire (remove) ALL objects from a sorcery memory cache.
71 </synopsis>
72 <syntax>
73 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
74 <parameter name="Cache" required="true">
75 <para>The name of the cache to expire all objects from.</para>
76 </parameter>
77 </syntax>
78 <description>
79 <para>Expires (removes) ALL objects from a sorcery memory cache.</para>
80 </description>
81 </manager>
82 <manager name="SorceryMemoryCacheStaleObject" language="en_US">
83 <since>
84 <version>13.5.0</version>
85 </since>
86 <synopsis>
87 Mark an object in a sorcery memory cache as stale.
88 </synopsis>
89 <syntax>
90 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
91 <parameter name="Cache" required="true">
92 <para>The name of the cache to mark the object as stale in.</para>
93 </parameter>
94 <parameter name="Object" required="true">
95 <para>The name of the object to mark as stale.</para>
96 </parameter>
97 <parameter name="Reload" required="false">
98 <para>If true, then immediately reload the object from the backend cache instead of waiting for the next retrieval</para>
99 </parameter>
100 </syntax>
101 <description>
102 <para>Marks an object as stale within a sorcery memory cache.</para>
103 </description>
104 </manager>
105 <manager name="SorceryMemoryCacheStale" language="en_US">
106 <since>
107 <version>13.5.0</version>
108 </since>
109 <synopsis>
110 Marks ALL objects in a sorcery memory cache as stale.
111 </synopsis>
112 <syntax>
113 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
114 <parameter name="Cache" required="true">
115 <para>The name of the cache to mark all object as stale in.</para>
116 </parameter>
117 </syntax>
118 <description>
119 <para>Marks ALL objects in a sorcery memory cache as stale.</para>
120 </description>
121 </manager>
122 <manager name="SorceryMemoryCachePopulate" language="en_US">
123 <since>
124 <version>13.7.0</version>
125 </since>
126 <synopsis>
127 Expire all objects from a memory cache and populate it with all objects from the backend.
128 </synopsis>
129 <syntax>
130 <xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
131 <parameter name="Cache" required="true">
132 <para>The name of the cache to populate.</para>
133 </parameter>
134 </syntax>
135 <description>
136 <para>Expires all objects from a memory cache and populate it with all objects from the backend.</para>
137 </description>
138 </manager>
139 ***/
140
141/*! \brief Structure for storing a memory cache */
143 /*! \brief The name of the memory cache */
144 char *name;
145 /*! \brief Objects in the cache */
147 /*! \brief The maximum number of objects permitted in the cache, 0 if no limit */
148 unsigned int maximum_objects;
149 /*! \brief The maximum time (in seconds) an object will stay in the cache, 0 if no limit */
151 /*! \brief The amount of time (in seconds) before an object is marked as stale, 0 if disabled */
153 /*! \brief Whether all objects are expired when the object type is reloaded, 0 if disabled */
154 unsigned int expire_on_reload;
155 /*! \brief Whether this is a cache of the entire backend, 0 if disabled */
156 unsigned int full_backend_cache;
157 /*! \brief Heap of cached objects. Oldest object is at the top. */
159 /*! \brief Scheduler item for expiring oldest object. */
161 /*! \brief scheduler id of stale update task */
163 /*! \brief An unreffed pointer to the sorcery instance, accessible only with lock held */
164 const struct ast_sorcery *sorcery;
165 /*! \brief The type of object we are caching */
167 /*! TRUE if trying to stop the oldest object expiration scheduler item. */
168 unsigned int del_expire:1;
169#ifdef TEST_FRAMEWORK
170 /*! \brief Variable used to indicate we should notify a test when we reach empty */
171 unsigned int cache_notify;
172 /*! \brief Mutex lock used for signaling when the cache has reached empty */
174 /*! \brief Condition used for signaling when the cache has reached empty */
176 /*! \brief Variable that is set when the cache has reached empty */
177 unsigned int cache_completed;
178#endif
179};
180
181/*! \brief Structure for stored a cached object */
183 /*! \brief The cached object */
184 void *object;
185 /*! \brief The time at which the object was created */
186 struct timeval created;
187 /*! \brief index required by heap */
189 /*! \brief scheduler id of stale update task */
191 /*! \brief Cached objectset for field and regex retrieval */
193};
194
195/*! \brief Structure used for fields comparison */
197 /*! \brief Pointer to the sorcery structure */
198 const struct ast_sorcery *sorcery;
199 /*! \brief The sorcery memory cache */
201 /*! \brief Pointer to the fields to check */
202 const struct ast_variable *fields;
203 /*! \brief Regular expression for checking object id */
204 regex_t *regex;
205 /*! \brief Prefix for matching object id */
206 const char *prefix;
207 /*! \brief Prefix length in bytes for matching object id */
208 const size_t prefix_len;
209 /*! \brief Optional container to put object into */
211};
212
213static void *sorcery_memory_cache_open(const char *data);
214static int sorcery_memory_cache_create(const struct ast_sorcery *sorcery, void *data, void *object);
215static void sorcery_memory_cache_load(void *data, const struct ast_sorcery *sorcery, const char *type);
216static void sorcery_memory_cache_reload(void *data, const struct ast_sorcery *sorcery, const char *type);
217static void *sorcery_memory_cache_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type,
218 const char *id);
219static void *sorcery_memory_cache_retrieve_fields(const struct ast_sorcery *sorcery, void *data, const char *type,
220 const struct ast_variable *fields);
221static void sorcery_memory_cache_retrieve_multiple(const struct ast_sorcery *sorcery, void *data, const char *type,
222 struct ao2_container *objects, const struct ast_variable *fields);
223static void sorcery_memory_cache_retrieve_regex(const struct ast_sorcery *sorcery, void *data, const char *type,
224 struct ao2_container *objects, const char *regex);
225static void sorcery_memory_cache_retrieve_prefix(const struct ast_sorcery *sorcery, void *data, const char *type,
226 struct ao2_container *objects, const char *prefix, const size_t prefix_len);
227static int sorcery_memory_cache_delete(const struct ast_sorcery *sorcery, void *data, void *object);
228static void sorcery_memory_cache_close(void *data);
229
231 .name = "memory_cache",
239 .retrieve_fields = sorcery_memory_cache_retrieve_fields,
240 .retrieve_multiple = sorcery_memory_cache_retrieve_multiple,
241 .retrieve_regex = sorcery_memory_cache_retrieve_regex,
242 .retrieve_prefix = sorcery_memory_cache_retrieve_prefix,
244};
245
246/*! \brief The bucket size for the container of caches */
247#define CACHES_CONTAINER_BUCKET_SIZE 53
248
249/*! \brief The default bucket size for the container of objects in the cache */
250#define CACHE_CONTAINER_BUCKET_SIZE 53
251
252/*! \brief Height of heap for cache object heap. Allows 31 initial objects */
253#define CACHE_HEAP_INIT_HEIGHT 5
254
255/*! \brief Container of created caches */
256static struct ao2_container *caches;
257
258/*! \brief Scheduler for cache management */
260
261#define PASSTHRU_UPDATE_THREAD_ID 0x5EED1E55
263
264static int is_passthru_update(void)
265{
266 uint32_t *passthru_update_thread_id;
267
268 passthru_update_thread_id = ast_threadstorage_get(&passthru_update_id_storage,
269 sizeof(*passthru_update_thread_id));
270 if (!passthru_update_thread_id) {
271 return 0;
272 }
273
274 return *passthru_update_thread_id == PASSTHRU_UPDATE_THREAD_ID;
275}
276
277static void set_passthru_update(uint32_t value)
278{
279 uint32_t *passthru_update_thread_id;
280
281 passthru_update_thread_id = ast_threadstorage_get(&passthru_update_id_storage,
282 sizeof(*passthru_update_thread_id));
283 if (!passthru_update_thread_id) {
284 ast_log(LOG_ERROR, "Could not set passthru update ID for sorcery memory cache thread\n");
285 return;
286 }
287
288 *passthru_update_thread_id = value;
289}
290
291static void start_passthru_update(void)
292{
294}
295
296static void end_passthru_update(void)
297{
299}
300
301/*!
302 * \internal
303 * \brief Hashing function for the container holding caches
304 *
305 * \param obj A sorcery memory cache or name of one
306 * \param flags Hashing flags
307 *
308 * \return The hash of the memory cache name
309 */
310static int sorcery_memory_cache_hash(const void *obj, int flags)
311{
312 const struct sorcery_memory_cache *cache = obj;
313 const char *name = obj;
314 int hash;
315
316 switch (flags & OBJ_SEARCH_MASK) {
317 default:
319 name = cache->name;
320 /* Fall through */
321 case OBJ_SEARCH_KEY:
322 hash = ast_str_hash(name);
323 break;
325 /* Should never happen in hash callback. */
326 ast_assert(0);
327 hash = 0;
328 break;
329 }
330 return hash;
331}
332
333/*!
334 * \internal
335 * \brief Comparison function for the container holding caches
336 *
337 * \param obj A sorcery memory cache
338 * \param arg A sorcery memory cache, or name of one
339 * \param flags Comparison flags
340 *
341 * \retval CMP_MATCH if the name is the same
342 * \retval 0 if the name does not match
343 */
344static int sorcery_memory_cache_cmp(void *obj, void *arg, int flags)
345{
346 const struct sorcery_memory_cache *left = obj;
347 const struct sorcery_memory_cache *right = arg;
348 const char *right_name = arg;
349 int cmp;
350
351 switch (flags & OBJ_SEARCH_MASK) {
352 default:
354 right_name = right->name;
355 /* Fall through */
356 case OBJ_SEARCH_KEY:
357 cmp = strcmp(left->name, right_name);
358 break;
360 cmp = strncmp(left->name, right_name, strlen(right_name));
361 break;
362 }
363 return cmp ? 0 : CMP_MATCH;
364}
365
366/*!
367 * \internal
368 * \brief Hashing function for the container holding cached objects
369 *
370 * \param obj A cached object or id of one
371 * \param flags Hashing flags
372 *
373 * \return The hash of the cached object id
374 */
375static int sorcery_memory_cached_object_hash(const void *obj, int flags)
376{
377 const struct sorcery_memory_cached_object *cached = obj;
378 const char *name = obj;
379 int hash;
380
381 switch (flags & OBJ_SEARCH_MASK) {
382 default:
385 /* Fall through */
386 case OBJ_SEARCH_KEY:
387 hash = ast_str_hash(name);
388 break;
390 /* Should never happen in hash callback. */
391 ast_assert(0);
392 hash = 0;
393 break;
394 }
395 return hash;
396}
397
398/*!
399 * \internal
400 * \brief Comparison function for the container holding cached objects
401 *
402 * \param obj A cached object
403 * \param arg A cached object, or id of one
404 * \param flags Comparison flags
405 *
406 * \retval CMP_MATCH if the id is the same
407 * \retval 0 if the id does not match
408 */
409static int sorcery_memory_cached_object_cmp(void *obj, void *arg, int flags)
410{
411 struct sorcery_memory_cached_object *left = obj;
412 struct sorcery_memory_cached_object *right = arg;
413 const char *right_name = arg;
414 int cmp;
415
416 switch (flags & OBJ_SEARCH_MASK) {
417 default:
419 right_name = ast_sorcery_object_get_id(right->object);
420 /* Fall through */
421 case OBJ_SEARCH_KEY:
422 cmp = strcmp(ast_sorcery_object_get_id(left->object), right_name);
423 break;
425 cmp = strncmp(ast_sorcery_object_get_id(left->object), right_name, strlen(right_name));
426 break;
427 }
428 return cmp ? 0 : CMP_MATCH;
429}
430
431/*!
432 * \internal
433 * \brief Destructor function for a sorcery memory cache
434 *
435 * \param obj A sorcery memory cache
436 */
438{
439 struct sorcery_memory_cache *cache = obj;
440
441 ast_free(cache->name);
442 if (cache->object_heap) {
443 ast_heap_destroy(cache->object_heap);
444 }
445 ao2_cleanup(cache->objects);
446 ast_free(cache->object_type);
447}
448
449/*!
450 * \internal
451 * \brief Destructor function for sorcery memory cached objects
452 *
453 * \param obj A sorcery memory cached object
454 */
456{
457 struct sorcery_memory_cached_object *cached = obj;
458
459 ao2_cleanup(cached->object);
461}
462
464
465/*!
466 * \internal
467 * \brief Remove an object from the cache.
468 *
469 * This removes the item from both the hashtable and the heap.
470 *
471 * \pre cache->objects is write-locked
472 *
473 * \param cache The cache from which the object is being removed.
474 * \param id The sorcery object id of the object to remove.
475 * \param reschedule Reschedule cache expiration if this was the oldest object.
476 *
477 * \retval 0 Success
478 * \retval non-zero Failure
479 */
480static int remove_from_cache(struct sorcery_memory_cache *cache, const char *id, int reschedule)
481{
482 struct sorcery_memory_cached_object *hash_object;
483 struct sorcery_memory_cached_object *oldest_object;
484 struct sorcery_memory_cached_object *heap_object;
485
486 hash_object = ao2_find(cache->objects, id, OBJ_SEARCH_KEY | OBJ_UNLINK | OBJ_NOLOCK);
487 if (!hash_object) {
488 return -1;
489 }
490
491 ast_assert(!strcmp(ast_sorcery_object_get_id(hash_object->object), id));
492
493 oldest_object = ast_heap_peek(cache->object_heap, 1);
494 heap_object = ast_heap_remove(cache->object_heap, hash_object);
495
496 ast_assert(heap_object == hash_object);
497
498 ao2_ref(hash_object, -1);
499
500 if (reschedule && (oldest_object == heap_object)) {
502 }
503
504 return 0;
505}
506
507/*!
508 * \internal
509 * \brief Scheduler callback invoked to expire old objects
510 *
511 * \param data The opaque callback data (in our case, the memory cache)
512 */
513static int expire_objects_from_cache(const void *data)
514{
515 struct sorcery_memory_cache *cache = (struct sorcery_memory_cache *)data;
516 struct sorcery_memory_cached_object *cached;
517
518 /*
519 * We need to do deadlock avoidance between a non-scheduler thread
520 * blocking when trying to delete the scheduled entry for this
521 * callback because the scheduler thread is running this callback
522 * and this callback waiting for the cache->objects container lock
523 * that the blocked non-scheduler thread already holds.
524 */
525 while (ao2_trywrlock(cache->objects)) {
526 if (cache->del_expire) {
527 cache->expire_id = -1;
528 ao2_ref(cache, -1);
529 return 0;
530 }
531 sched_yield();
532 }
533
534 cache->expire_id = -1;
535
536 /* This is an optimization for objects which have been cached close to each other */
537 while ((cached = ast_heap_peek(cache->object_heap, 1))) {
538 int expiration;
539
540 expiration = ast_tvdiff_ms(ast_tvadd(cached->created, ast_samp2tv(cache->object_lifetime_maximum, 1)), ast_tvnow());
541
542 /* If the current oldest object has not yet expired stop and reschedule for it */
543 if (expiration > 0) {
544 break;
545 }
546
548 }
549
551
552 ao2_unlock(cache->objects);
553
554 ao2_ref(cache, -1);
555
556 return 0;
557}
558
559/*!
560 * \internal
561 * \brief Remove all objects from the cache.
562 *
563 * This removes ALL objects from both the hash table and heap.
564 *
565 * \pre cache->objects is write-locked
566 *
567 * \param cache The cache to empty.
568 */
570{
571 while (ast_heap_pop(cache->object_heap)) {
572 }
573
575 NULL, NULL);
576
577 cache->del_expire = 1;
578 AST_SCHED_DEL_UNREF(sched, cache->expire_id, ao2_ref(cache, -1));
579 cache->del_expire = 0;
580}
581
582/*!
583 * \internal
584 * \brief AO2 callback function for making an object stale immediately
585 *
586 * This changes the creation time of an object so it appears as though it is stale immediately.
587 *
588 * \param obj The cached object
589 * \param arg The cache itself
590 * \param flags Unused flags
591 */
592static int object_stale_callback(void *obj, void *arg, int flags)
593{
594 struct sorcery_memory_cached_object *cached = obj;
595 struct sorcery_memory_cache *cache = arg;
596
597 /* Since our granularity is seconds it's possible for something to retrieve us within a window
598 * where we wouldn't be treated as stale. To ensure that doesn't happen we use the configured stale
599 * time plus a second.
600 */
601 cached->created = ast_tvsub(cached->created, ast_samp2tv(cache->object_lifetime_stale + 1, 1));
602
603 return CMP_MATCH;
604}
605
606/*!
607 * \internal
608 * \brief Mark an object as stale explicitly.
609 *
610 * This changes the creation time of an object so it appears as though it is stale immediately.
611 *
612 * \pre cache->objects is read-locked
613 *
614 * \param cache The cache the object is in
615 * \param id The unique identifier of the object
616 *
617 * \retval 0 success
618 * \retval -1 failure
619 */
621{
622 struct sorcery_memory_cached_object *cached;
623
624 cached = ao2_find(cache->objects, id, OBJ_SEARCH_KEY | OBJ_NOLOCK);
625 if (!cached) {
626 return -1;
627 }
628
629 ast_assert(!strcmp(ast_sorcery_object_get_id(cached->object), id));
630
631 object_stale_callback(cached, cache, 0);
632 ao2_ref(cached, -1);
633
634 return 0;
635}
636
637/*!
638 * \internal
639 * \brief Mark all objects as stale within a cache.
640 *
641 * This changes the creation time of ALL objects so they appear as though they are stale.
642 *
643 * \pre cache->objects is read-locked
644 *
645 * \param cache
646 */
648{
650}
651
652/*!
653 * \internal
654 * \brief Schedule a callback for cached object expiration.
655 *
656 * \pre cache->objects is write-locked
657 *
658 * \param cache The cache that is having its callback scheduled.
659 *
660 * \retval 0 success
661 * \retval -1 failure
662 */
664{
665 struct sorcery_memory_cached_object *cached;
666 int expiration = 0;
667
668 if (!cache->object_lifetime_maximum) {
669 return 0;
670 }
671
672 cache->del_expire = 1;
673 AST_SCHED_DEL_UNREF(sched, cache->expire_id, ao2_ref(cache, -1));
674 cache->del_expire = 0;
675
676 cached = ast_heap_peek(cache->object_heap, 1);
677 if (!cached) {
678#ifdef TEST_FRAMEWORK
679 ast_mutex_lock(&cache->lock);
680 cache->cache_completed = 1;
681 ast_cond_signal(&cache->cond);
682 ast_mutex_unlock(&cache->lock);
683#endif
684 return 0;
685 }
686
687 expiration = MAX(ast_tvdiff_ms(ast_tvadd(cached->created, ast_samp2tv(cache->object_lifetime_maximum, 1)), ast_tvnow()),
688 1);
689
691 if (cache->expire_id < 0) {
692 ao2_ref(cache, -1);
693 return -1;
694 }
695
696 return 0;
697}
698
699/*!
700 * \internal
701 * \brief Remove the oldest item from the cache.
702 *
703 * \pre cache->objects is write-locked
704 *
705 * \param cache The cache from which to remove the oldest object
706 *
707 * \retval 0 Success
708 * \retval non-zero Failure
709 */
711{
712 struct sorcery_memory_cached_object *heap_old_object;
713 struct sorcery_memory_cached_object *hash_old_object;
714
715 heap_old_object = ast_heap_pop(cache->object_heap);
716 if (!heap_old_object) {
717 return -1;
718 }
719 hash_old_object = ao2_find(cache->objects, heap_old_object,
721
722 ast_assert(heap_old_object == hash_old_object);
723
724 ao2_ref(hash_old_object, -1);
725
727
728 return 0;
729}
730
731/*!
732 * \internal
733 * \brief Add a new object to the cache.
734 *
735 * \pre cache->objects is write-locked
736 *
737 * \param cache The cache in which to add the new object
738 * \param cached_object The object to add to the cache
739 *
740 * \retval 0 Success
741 * \retval non-zero Failure
742 */
744 struct sorcery_memory_cached_object *cached_object)
745{
746 struct sorcery_memory_cached_object *front;
747
748 if (!ao2_link_flags(cache->objects, cached_object, OBJ_NOLOCK)) {
749 return -1;
750 }
751
752 if (cache->full_backend_cache && (front = ast_heap_peek(cache->object_heap, 1))) {
753 /* For a full backend cache all objects share the same lifetime */
754 cached_object->created = front->created;
755 }
756
757 if (ast_heap_push(cache->object_heap, cached_object)) {
758 ao2_find(cache->objects, cached_object,
760 return -1;
761 }
762
763 if (cache->expire_id == -1) {
765 }
766
767 return 0;
768}
769
770/*!
771 * \internal
772 * \brief Allocate a cached object for caching an object
773 *
774 * \param sorcery The sorcery instance
775 * \param cache The sorcery memory cache
776 * \param object The object to cache
777 *
778 * \retval non-NULL success
779 * \retval NULL failure
780 */
782 const struct sorcery_memory_cache *cache, void *object)
783{
784 struct sorcery_memory_cached_object *cached;
785
786 cached = ao2_alloc(sizeof(*cached), sorcery_memory_cached_object_destructor);
787 if (!cached) {
788 return NULL;
789 }
790
791 cached->object = ao2_bump(object);
792 cached->created = ast_tvnow();
793 cached->stale_update_sched_id = -1;
794
795 if (cache->full_backend_cache) {
796 /* A cached objectset allows us to easily perform all retrieval operations in a
797 * minimal of time.
798 */
800 if (!cached->objectset) {
801 ao2_ref(cached, -1);
802 return NULL;
803 }
804 }
805
806 return cached;
807}
808
809/*!
810 * \internal
811 * \brief Callback function to cache an object in a memory cache
812 *
813 * \param sorcery The sorcery instance
814 * \param data The sorcery memory cache
815 * \param object The object to cache
816 *
817 * \retval 0 success
818 * \retval -1 failure
819 */
820static int sorcery_memory_cache_create(const struct ast_sorcery *sorcery, void *data, void *object)
821{
822 struct sorcery_memory_cache *cache = data;
823 struct sorcery_memory_cached_object *cached;
824
826 if (!cached) {
827 return -1;
828 }
829
830 /* As there is no guarantee that this won't be called by multiple threads wanting to cache
831 * the same object we remove any old ones, which turns this into a create/update function
832 * in reality. As well since there's no guarantee that the object in the cache is the same
833 * one here we remove any old objects using the object identifier.
834 */
835
836 ao2_wrlock(cache->objects);
838 if (cache->maximum_objects && ao2_container_count(cache->objects) >= cache->maximum_objects) {
840 ast_log(LOG_ERROR, "Unable to make room in cache for sorcery object '%s'.\n",
842 ao2_unlock(cache->objects);
843 ao2_ref(cached, -1);
844 return -1;
845 }
846 ast_assert(ao2_container_count(cache->objects) != cache->maximum_objects);
847 }
848 if (add_to_cache(cache, cached)) {
849 ast_log(LOG_ERROR, "Unable to add object '%s' to the cache\n",
851 ao2_unlock(cache->objects);
852 ao2_ref(cached, -1);
853 return -1;
854 }
855 ao2_unlock(cache->objects);
856
857 ao2_ref(cached, -1);
858 return 0;
859}
860
861/*!
862 * \internal
863 * \brief AO2 callback function for adding an object to a memory cache
864 *
865 * \param obj The cached object
866 * \param arg The sorcery instance
867 * \param data The cache itself
868 * \param flags Unused flags
869 */
870static int object_add_to_cache_callback(void *obj, void *arg, void *data, int flags)
871{
872 struct sorcery_memory_cache *cache = data;
873 struct sorcery_memory_cached_object *cached;
874
875 cached = sorcery_memory_cached_object_alloc(arg, cache, obj);
876 if (!cached) {
877 return CMP_STOP;
878 }
879
880 add_to_cache(cache, cached);
881 ao2_ref(cached, -1);
882
883 return 0;
884}
885
889 char *type;
890};
891
893{
895
896 ao2_cleanup(task_data->cache);
898 ast_free(task_data->type);
899}
900
902 struct sorcery_memory_cache *cache, const char *type)
903{
905
908 if (!task_data) {
909 return NULL;
910 }
911
912 task_data->sorcery = ao2_bump(sorcery);
913 task_data->cache = ao2_bump(cache);
914 task_data->type = ast_strdup(type);
915 if (!task_data->type) {
916 ao2_ref(task_data, -1);
917 return NULL;
918 }
919
920 return task_data;
921}
922
923static int stale_cache_update(const void *data)
924{
926 struct ao2_container *backend_objects;
927
929 backend_objects = ast_sorcery_retrieve_by_fields(task_data->sorcery, task_data->type,
932
933 if (!backend_objects) {
934 task_data->cache->stale_update_sched_id = -1;
935 ao2_ref(task_data, -1);
936 return 0;
937 }
938
939 if (task_data->cache->maximum_objects && ao2_container_count(backend_objects) >= task_data->cache->maximum_objects) {
940 ast_log(LOG_ERROR, "The backend contains %d objects while the sorcery memory cache '%s' is explicitly configured to only allow %d\n",
941 ao2_container_count(backend_objects), task_data->cache->name, task_data->cache->maximum_objects);
942 task_data->cache->stale_update_sched_id = -1;
943 ao2_ref(task_data, -1);
944 return 0;
945 }
946
947 ao2_wrlock(task_data->cache->objects);
950 task_data->sorcery, task_data->cache);
951
952 /* If the number of cached objects does not match the number of backend objects we encountered a memory allocation
953 * failure and the cache is incomplete, so drop everything and fall back to querying the backend directly
954 * as it may be able to provide what is wanted.
955 */
956 if (ao2_container_count(task_data->cache->objects) != ao2_container_count(backend_objects)) {
957 ast_log(LOG_WARNING, "The backend contains %d objects while only %d could be added to sorcery memory cache '%s'\n",
958 ao2_container_count(backend_objects), ao2_container_count(task_data->cache->objects), task_data->cache->name);
960 }
961
962 ao2_unlock(task_data->cache->objects);
963 ao2_ref(backend_objects, -1);
964
965 task_data->cache->stale_update_sched_id = -1;
966 ao2_ref(task_data, -1);
967
968 return 0;
969}
970
974 void *object;
975};
976
978{
979 struct stale_update_task_data *task_data = obj;
980
981 ao2_cleanup(task_data->cache);
982 ao2_cleanup(task_data->object);
984}
985
987 struct sorcery_memory_cache *cache, const char *type, void *object)
988{
990
993 if (!task_data) {
994 return NULL;
995 }
996
997 task_data->sorcery = ao2_bump(sorcery);
998 task_data->cache = ao2_bump(cache);
999 task_data->object = ao2_bump(object);
1000
1001 return task_data;
1002}
1003
1004static int stale_item_update(const void *data)
1005{
1007 void *object;
1008
1010
1011 object = ast_sorcery_retrieve_by_id(task_data->sorcery,
1014 if (!object) {
1015 ast_debug(1, "Backend no longer has object type '%s' ID '%s'. Removing from cache\n",
1019 task_data->object);
1020 } else {
1021 ast_debug(1, "Refreshing stale cache object type '%s' ID '%s'\n",
1025 object);
1026 ao2_ref(object, -1);
1027 }
1028
1029 ast_test_suite_event_notify("SORCERY_MEMORY_CACHE_REFRESHED", "Cache: %s\r\nType: %s\r\nName: %s\r\n",
1030 task_data->cache->name, ast_sorcery_object_get_type(task_data->object),
1032
1033 ao2_ref(task_data, -1);
1035
1036 return 0;
1037}
1038
1039/*!
1040 * \internal
1041 * \brief Populate the cache with all objects from the backend
1042 *
1043 * \pre cache->objects is write-locked
1044 *
1045 * \param sorcery The sorcery instance
1046 * \param type The type of object
1047 * \param cache The sorcery memory cache
1048 */
1049static void memory_cache_populate(const struct ast_sorcery *sorcery, const char *type, struct sorcery_memory_cache *cache)
1050{
1051 struct ao2_container *backend_objects;
1052
1056
1057 if (!backend_objects) {
1058 /* This will occur in off-nominal memory allocation failure scenarios */
1059 return;
1060 }
1061
1062 if (cache->maximum_objects && ao2_container_count(backend_objects) >= cache->maximum_objects) {
1063 ast_log(LOG_ERROR, "The backend contains %d objects while the sorcery memory cache '%s' is explicitly configured to only allow %d\n",
1064 ao2_container_count(backend_objects), cache->name, cache->maximum_objects);
1065 return;
1066 }
1067
1069 (struct ast_sorcery*)sorcery, cache);
1070
1071 /* If the number of cached objects does not match the number of backend objects we encountered a memory allocation
1072 * failure and the cache is incomplete, so drop everything and fall back to querying the backend directly
1073 * as it may be able to provide what is wanted.
1074 */
1075 if (ao2_container_count(cache->objects) != ao2_container_count(backend_objects)) {
1076 ast_log(LOG_WARNING, "The backend contains %d objects while only %d could be added to sorcery memory cache '%s'\n",
1077 ao2_container_count(backend_objects), ao2_container_count(cache->objects), cache->name);
1079 }
1080
1081 ao2_ref(backend_objects, -1);
1082}
1083
1084/*!
1085 * \internal
1086 * \brief Determine if a full backend cache update is needed and do it
1087 *
1088 * \param sorcery The sorcery instance
1089 * \param type The type of object
1090 * \param cache The sorcery memory cache
1091 */
1092static void memory_cache_full_update(const struct ast_sorcery *sorcery, const char *type, struct sorcery_memory_cache *cache)
1093{
1094 if (!cache->full_backend_cache) {
1095 return;
1096 }
1097
1098 ao2_wrlock(cache->objects);
1099 if (!ao2_container_count(cache->objects)) {
1101 }
1102 ao2_unlock(cache->objects);
1103}
1104
1105/*!
1106 * \internal
1107 * \brief Queue a full cache update
1108 *
1109 * \param sorcery The sorcery instance
1110 * \param cache The sorcery memory cache
1111 * \param type The type of object
1112 */
1114 const char *type)
1115{
1116 ao2_wrlock(cache->objects);
1117 if (cache->stale_update_sched_id == -1) {
1119
1121 cache, type);
1122 if (task_data) {
1123 cache->stale_update_sched_id = ast_sched_add(sched, 1,
1125 }
1126 if (cache->stale_update_sched_id < 0) {
1128 }
1129 }
1130 ao2_unlock(cache->objects);
1131}
1132
1133/*!
1134 * \internal
1135 * \brief Queue a stale object update
1136 *
1137 * \param sorcery The sorcery instance
1138 * \param cache The sorcery memory cache
1139 * \param cached The cached object
1140 */
1142 struct sorcery_memory_cached_object *cached)
1143{
1144 ao2_lock(cached);
1145 if (cached->stale_update_sched_id == -1) {
1147
1149 cache, ast_sorcery_object_get_type(cached->object), cached->object);
1150 if (task_data) {
1151 ast_debug(1, "Cached sorcery object type '%s' ID '%s' is stale. Refreshing\n",
1155 }
1156 if (cached->stale_update_sched_id < 0) {
1158 ast_log(LOG_ERROR, "Unable to update stale cached object type '%s', ID '%s'.\n",
1160 }
1161 }
1162 ao2_unlock(cached);
1163}
1164
1165/*!
1166 * \internal
1167 * \brief Check whether an object (or cache) is stale and queue an update
1168 *
1169 * \param sorcery The sorcery instance
1170 * \param cache The sorcery memory cache
1171 * \param cached The cached object
1172 */
1174 struct sorcery_memory_cached_object *cached)
1175{
1176 struct timeval elapsed;
1177
1178 if (!cache->object_lifetime_stale) {
1179 return;
1180 }
1181
1182 /* For a full cache as every object has the same expiration/staleness we can do the same check */
1183 elapsed = ast_tvsub(ast_tvnow(), cached->created);
1184
1185 if (elapsed.tv_sec < cache->object_lifetime_stale) {
1186 return;
1187 }
1188
1189 if (cache->full_backend_cache) {
1191 } else {
1193 }
1194
1195}
1196
1197/*!
1198 * \internal
1199 * \brief Check whether the entire cache is stale or not and queue an update
1200 *
1201 * \param sorcery The sorcery instance
1202 * \param cache The sorcery memory cache
1203 *
1204 * \note Unlike \ref memory_cache_stale_check this does not require an explicit object
1205 */
1207{
1208 struct sorcery_memory_cached_object *cached;
1209
1210 ao2_rdlock(cache->objects);
1211 cached = ao2_bump(ast_heap_peek(cache->object_heap, 1));
1212 ao2_unlock(cache->objects);
1213
1214 if (!cached) {
1215 return;
1216 }
1217
1219 ao2_ref(cached, -1);
1220}
1221
1222/*!
1223 * \internal
1224 * \brief Callback function to retrieve an object from a memory cache
1225 *
1226 * \param sorcery The sorcery instance
1227 * \param data The sorcery memory cache
1228 * \param type The type of the object to retrieve
1229 * \param id The id of the object to retrieve
1230 *
1231 * \retval non-NULL success
1232 * \retval NULL failure
1233 */
1234static void *sorcery_memory_cache_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id)
1235{
1236 struct sorcery_memory_cache *cache = data;
1237 struct sorcery_memory_cached_object *cached;
1238 void *object;
1239
1240 if (is_passthru_update()) {
1241 return NULL;
1242 }
1243
1245
1246 cached = ao2_find(cache->objects, id, OBJ_SEARCH_KEY);
1247 if (!cached) {
1248 return NULL;
1249 }
1250
1251 ast_assert(!strcmp(ast_sorcery_object_get_id(cached->object), id));
1252
1254
1255 object = ao2_bump(cached->object);
1256 ao2_ref(cached, -1);
1257
1258 return object;
1259}
1260
1261/*!
1262 * \internal
1263 * \brief AO2 callback function for comparing a retrieval request and finding applicable objects
1264 *
1265 * \param obj The cached object
1266 * \param arg The comparison parameters
1267 * \param flags Unused flags
1268 */
1269static int sorcery_memory_cache_fields_cmp(void *obj, void *arg, int flags)
1270{
1271 struct sorcery_memory_cached_object *cached = obj;
1272 const struct sorcery_memory_cache_fields_cmp_params *params = arg;
1274
1275 if (params->regex) {
1276 /* If a regular expression has been provided see if it matches, otherwise move on */
1277 if (!regexec(params->regex, ast_sorcery_object_get_id(cached->object), 0, NULL, 0)) {
1278 ao2_link(params->container, cached->object);
1279 }
1280 return 0;
1281 } else if (params->prefix) {
1282 if (!strncmp(params->prefix, ast_sorcery_object_get_id(cached->object), params->prefix_len)) {
1283 ao2_link(params->container, cached->object);
1284 }
1285 return 0;
1286 } else if (params->fields &&
1287 (!ast_variable_lists_match(cached->objectset, params->fields, 0))) {
1288 /* If we can't turn the object into an object set OR if differences exist between the fields
1289 * passed in and what are present on the object they are not a match.
1290 */
1291 return 0;
1292 }
1293
1294 if (params->container) {
1295 ao2_link(params->container, cached->object);
1296
1297 /* As multiple objects are being returned keep going */
1298 return 0;
1299 } else {
1300 /* Immediately stop and return, we only want a single object */
1301 return CMP_MATCH | CMP_STOP;
1302 }
1303}
1304
1305/*!
1306 * \internal
1307 * \brief Callback function to retrieve a single object based on fields
1308 *
1309 * \param sorcery The sorcery instance
1310 * \param data The sorcery memory cache
1311 * \param type The type of the object to retrieve
1312 * \param fields Any explicit fields to search for
1313 */
1314static void *sorcery_memory_cache_retrieve_fields(const struct ast_sorcery *sorcery, void *data, const char *type,
1315 const struct ast_variable *fields)
1316{
1317 struct sorcery_memory_cache *cache = data;
1319 .sorcery = sorcery,
1320 .cache = cache,
1321 .fields = fields,
1322 };
1323 struct sorcery_memory_cached_object *cached;
1324 void *object = NULL;
1325
1326 if (is_passthru_update() || !cache->full_backend_cache || !fields) {
1327 return NULL;
1328 }
1329
1330 cached = ao2_callback(cache->objects, 0, sorcery_memory_cache_fields_cmp, &params);
1331
1332 if (cached) {
1334 object = ao2_bump(cached->object);
1335 ao2_ref(cached, -1);
1336 }
1337
1338 return object;
1339}
1340
1341/*!
1342 * \internal
1343 * \brief Callback function to retrieve multiple objects from a memory cache
1344 *
1345 * \param sorcery The sorcery instance
1346 * \param data The sorcery memory cache
1347 * \param type The type of the object to retrieve
1348 * \param objects Container to place the objects into
1349 * \param fields Any explicit fields to search for
1350 */
1351static void sorcery_memory_cache_retrieve_multiple(const struct ast_sorcery *sorcery, void *data, const char *type,
1352 struct ao2_container *objects, const struct ast_variable *fields)
1353{
1354 struct sorcery_memory_cache *cache = data;
1356 .sorcery = sorcery,
1357 .cache = cache,
1358 .fields = fields,
1359 .container = objects,
1360 };
1361
1362 if (is_passthru_update() || !cache->full_backend_cache) {
1363 return;
1364 }
1365
1367 ao2_callback(cache->objects, 0, sorcery_memory_cache_fields_cmp, &params);
1368
1369 if (ao2_container_count(objects)) {
1371 }
1372}
1373
1374/*!
1375 * \internal
1376 * \brief Callback function to retrieve multiple objects using a regex on the object id
1377 *
1378 * \param sorcery The sorcery instance
1379 * \param data The sorcery memory cache
1380 * \param type The type of the object to retrieve
1381 * \param objects Container to place the objects into
1382 * \param regex Regular expression to apply to the object id
1383 */
1384static void sorcery_memory_cache_retrieve_regex(const struct ast_sorcery *sorcery, void *data, const char *type,
1385 struct ao2_container *objects, const char *regex)
1386{
1387 struct sorcery_memory_cache *cache = data;
1388 regex_t expression;
1390 .sorcery = sorcery,
1391 .cache = cache,
1392 .container = objects,
1393 .regex = &expression,
1394 };
1395
1396 if (is_passthru_update() || !cache->full_backend_cache || regcomp(&expression, regex, REG_EXTENDED | REG_NOSUB)) {
1397 return;
1398 }
1399
1401 ao2_callback(cache->objects, 0, sorcery_memory_cache_fields_cmp, &params);
1402 regfree(&expression);
1403
1404 if (ao2_container_count(objects)) {
1406 }
1407}
1408
1409/*!
1410 * \internal
1411 * \brief Callback function to retrieve multiple objects whose id matches a prefix
1412 *
1413 * \param sorcery The sorcery instance
1414 * \param data The sorcery memory cache
1415 * \param type The type of the object to retrieve
1416 * \param objects Container to place the objects into
1417 * \param prefix, prefix_len Prefix to match against the object id
1418 */
1419static void sorcery_memory_cache_retrieve_prefix(const struct ast_sorcery *sorcery, void *data, const char *type,
1420 struct ao2_container *objects, const char *prefix, const size_t prefix_len)
1421{
1422 struct sorcery_memory_cache *cache = data;
1424 .sorcery = sorcery,
1425 .cache = cache,
1426 .container = objects,
1427 .prefix = prefix,
1428 .prefix_len = prefix_len,
1429 };
1430
1431 if (is_passthru_update() || !cache->full_backend_cache) {
1432 return;
1433 }
1434
1436 ao2_callback(cache->objects, 0, sorcery_memory_cache_fields_cmp, &params);
1437
1438 if (ao2_container_count(objects)) {
1440 }
1441}
1442
1443/*!
1444 * \internal
1445 * \brief Callback function to finish configuring the memory cache
1446 *
1447 * \param data The sorcery memory cache
1448 * \param sorcery The sorcery instance
1449 * \param type The type of object being loaded
1450 */
1451static void sorcery_memory_cache_load(void *data, const struct ast_sorcery *sorcery, const char *type)
1452{
1453 struct sorcery_memory_cache *cache = data;
1454
1455 /* If no name was explicitly specified generate one given the sorcery instance and object type */
1456 if (ast_strlen_zero(cache->name)) {
1458 }
1459
1461 ast_debug(1, "Memory cache '%s' associated with sorcery instance '%p' of module '%s' with object type '%s'\n",
1463
1464 cache->sorcery = sorcery;
1465 cache->object_type = ast_strdup(type);
1466}
1467
1468/*!
1469 * \internal
1470 * \brief Callback function to expire objects from the memory cache on reload (if configured)
1471 *
1472 * \param data The sorcery memory cache
1473 * \param sorcery The sorcery instance
1474 * \param type The type of object being reloaded
1475 */
1476static void sorcery_memory_cache_reload(void *data, const struct ast_sorcery *sorcery, const char *type)
1477{
1478 struct sorcery_memory_cache *cache = data;
1479
1480 if (!cache->expire_on_reload) {
1481 return;
1482 }
1483
1484 ao2_wrlock(cache->objects);
1486 ao2_unlock(cache->objects);
1487}
1488
1489/*!
1490 * \internal
1491 * \brief Function used to take an unsigned integer based configuration option and parse it
1492 *
1493 * \param value The string value of the configuration option
1494 * \param result The unsigned integer to place the result in
1495 *
1496 * \retval 0 failure
1497 * \retval 1 success
1498 */
1499static int configuration_parse_unsigned_integer(const char *value, unsigned int *result)
1500{
1501 if (ast_strlen_zero(value) || !strncmp(value, "-", 1)) {
1502 return 0;
1503 }
1504
1505 return sscanf(value, "%30u", result);
1506}
1507
1508static int age_cmp(void *a, void *b)
1509{
1510 return ast_tvcmp(((struct sorcery_memory_cached_object *) b)->created,
1511 ((struct sorcery_memory_cached_object *) a)->created);
1512}
1513
1514/*!
1515 * \internal
1516 * \brief Callback function to create a new sorcery memory cache using provided configuration
1517 *
1518 * \param data A stringified configuration for the memory cache
1519 *
1520 * \retval non-NULL success
1521 * \retval NULL failure
1522 */
1523static void *sorcery_memory_cache_open(const char *data)
1524{
1525 char *options = ast_strdupa(data), *option;
1527
1529 if (!cache) {
1530 return NULL;
1531 }
1532
1533 cache->expire_id = -1;
1534 cache->stale_update_sched_id = -1;
1535
1536 /* If no configuration options have been provided this memory cache will operate in a default
1537 * configuration.
1538 */
1539 while (!ast_strlen_zero(options) && (option = strsep(&options, ","))) {
1540 char *name = strsep(&option, "="), *value = option;
1541
1542 if (!strcasecmp(name, "name")) {
1543 if (ast_strlen_zero(value)) {
1544 ast_log(LOG_ERROR, "A name must be specified for the memory cache\n");
1545 return NULL;
1546 }
1547 ast_free(cache->name);
1548 cache->name = ast_strdup(value);
1549 } else if (!strcasecmp(name, "maximum_objects")) {
1550 if (configuration_parse_unsigned_integer(value, &cache->maximum_objects) != 1) {
1551 ast_log(LOG_ERROR, "Unsupported maximum objects value of '%s' used for memory cache\n",
1552 value);
1553 return NULL;
1554 }
1555 } else if (!strcasecmp(name, "object_lifetime_maximum")) {
1556 if (configuration_parse_unsigned_integer(value, &cache->object_lifetime_maximum) != 1) {
1557 ast_log(LOG_ERROR, "Unsupported object maximum lifetime value of '%s' used for memory cache\n",
1558 value);
1559 return NULL;
1560 }
1561 } else if (!strcasecmp(name, "object_lifetime_stale")) {
1562 if (configuration_parse_unsigned_integer(value, &cache->object_lifetime_stale) != 1) {
1563 ast_log(LOG_ERROR, "Unsupported object stale lifetime value of '%s' used for memory cache\n",
1564 value);
1565 return NULL;
1566 }
1567 } else if (!strcasecmp(name, "expire_on_reload")) {
1568 cache->expire_on_reload = ast_true(value);
1569 } else if (!strcasecmp(name, "full_backend_cache")) {
1570 cache->full_backend_cache = ast_true(value);
1571 } else {
1572 ast_log(LOG_ERROR, "Unsupported option '%s' used for memory cache\n", name);
1573 return NULL;
1574 }
1575 }
1576
1578 cache->maximum_objects ? cache->maximum_objects : CACHE_CONTAINER_BUCKET_SIZE,
1580 if (!cache->objects) {
1581 ast_log(LOG_ERROR, "Could not create a container to hold cached objects for memory cache\n");
1582 return NULL;
1583 }
1584
1586 offsetof(struct sorcery_memory_cached_object, __heap_index));
1587 if (!cache->object_heap) {
1588 ast_log(LOG_ERROR, "Could not create heap to hold cached objects\n");
1589 return NULL;
1590 }
1591
1592 /* The memory cache is not linked to the caches container until the load callback is invoked.
1593 * Linking occurs there so an intelligent cache name can be constructed using the module of
1594 * the sorcery instance and the specific object type if no cache name was specified as part
1595 * of the configuration.
1596 */
1597
1598 /* This is done as RAII_VAR will drop the reference */
1599 return ao2_bump(cache);
1600}
1601
1602/*!
1603 * \internal
1604 * \brief Callback function to delete an object from a memory cache
1605 *
1606 * \param sorcery The sorcery instance
1607 * \param data The sorcery memory cache
1608 * \param object The object to cache
1609 *
1610 * \retval 0 success
1611 * \retval -1 failure
1612 */
1613static int sorcery_memory_cache_delete(const struct ast_sorcery *sorcery, void *data, void *object)
1614{
1615 struct sorcery_memory_cache *cache = data;
1616 int res;
1617
1618 ao2_wrlock(cache->objects);
1620 ao2_unlock(cache->objects);
1621
1622 if (res) {
1623 ast_debug(1, "Unable to delete object '%s' from sorcery cache\n", ast_sorcery_object_get_id(object));
1624 }
1625
1626 return res;
1627}
1628
1629/*!
1630 * \internal
1631 * \brief Callback function to terminate a memory cache
1632 *
1633 * \param data The sorcery memory cache
1634 */
1635static void sorcery_memory_cache_close(void *data)
1636{
1637 struct sorcery_memory_cache *cache = data;
1638
1639 /* This can occur if a cache is created but never loaded */
1640 if (!ast_strlen_zero(cache->name)) {
1642 }
1643
1644 if (cache->object_lifetime_maximum) {
1645 /* If object lifetime support is enabled we need to explicitly drop all cached objects here
1646 * and stop the scheduled task. Failure to do so could potentially keep the cache around for
1647 * a prolonged period of time.
1648 */
1649 ao2_wrlock(cache->objects);
1651 ao2_unlock(cache->objects);
1652 }
1653
1654 if (cache->full_backend_cache) {
1655 ao2_wrlock(cache->objects);
1656 cache->sorcery = NULL;
1657 ao2_unlock(cache->objects);
1658 }
1659
1660 ao2_ref(cache, -1);
1661}
1662
1663/*!
1664 * \internal
1665 * \brief CLI tab completion for cache names
1666 */
1667static char *sorcery_memory_cache_complete_name(const char *word, int state)
1668{
1670 struct ao2_iterator it_caches;
1671 int wordlen = strlen(word);
1672 int which = 0;
1673 char *result = NULL;
1674
1675 it_caches = ao2_iterator_init(caches, 0);
1676 while ((cache = ao2_iterator_next(&it_caches))) {
1677 if (!strncasecmp(word, cache->name, wordlen)
1678 && ++which > state) {
1679 result = ast_strdup(cache->name);
1680 }
1681 ao2_ref(cache, -1);
1682 if (result) {
1683 break;
1684 }
1685 }
1686 ao2_iterator_destroy(&it_caches);
1687 return result;
1688}
1689
1690/*!
1691 * \internal
1692 * \brief CLI command implementation for 'sorcery memory cache show'
1693 */
1694static char *sorcery_memory_cache_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1695{
1697
1698 switch (cmd) {
1699 case CLI_INIT:
1700 e->command = "sorcery memory cache show";
1701 e->usage =
1702 "Usage: sorcery memory cache show <name>\n"
1703 " Show sorcery memory cache configuration and statistics.\n";
1704 return NULL;
1705 case CLI_GENERATE:
1706 if (a->pos == 4) {
1707 return sorcery_memory_cache_complete_name(a->word, a->n);
1708 } else {
1709 return NULL;
1710 }
1711 }
1712
1713 if (a->argc != 5) {
1714 return CLI_SHOWUSAGE;
1715 }
1716
1717 cache = ao2_find(caches, a->argv[4], OBJ_SEARCH_KEY);
1718 if (!cache) {
1719 ast_cli(a->fd, "Specified sorcery memory cache '%s' does not exist\n", a->argv[4]);
1720 return CLI_FAILURE;
1721 }
1722
1723 ast_cli(a->fd, "Sorcery memory cache: %s\n", cache->name);
1724 ast_cli(a->fd, "Number of objects within cache: %d\n", ao2_container_count(cache->objects));
1725 if (cache->maximum_objects) {
1726 ast_cli(a->fd, "Maximum allowed objects: %d\n", cache->maximum_objects);
1727 } else {
1728 ast_cli(a->fd, "There is no limit on the maximum number of objects in the cache\n");
1729 }
1730 if (cache->object_lifetime_maximum) {
1731 ast_cli(a->fd, "Number of seconds before object expires: %d\n", cache->object_lifetime_maximum);
1732 } else {
1733 ast_cli(a->fd, "Object expiration is not enabled - cached objects will not expire\n");
1734 }
1735 if (cache->object_lifetime_stale) {
1736 ast_cli(a->fd, "Number of seconds before object becomes stale: %d\n", cache->object_lifetime_stale);
1737 } else {
1738 ast_cli(a->fd, "Object staleness is not enabled - cached objects will not go stale\n");
1739 }
1740 ast_cli(a->fd, "Expire all objects on reload: %s\n", AST_CLI_ONOFF(cache->expire_on_reload));
1741
1742 ao2_ref(cache, -1);
1743
1744 return CLI_SUCCESS;
1745}
1746
1747/*! \brief Structure used to pass data for printing cached object information */
1749 /*! \brief The sorcery memory cache */
1751 /*! \brief The CLI arguments */
1753};
1754
1755/*!
1756 * \internal
1757 * \brief Callback function for displaying object within the cache
1758 */
1759static int sorcery_memory_cache_print_object(void *obj, void *arg, int flags)
1760{
1761#define FORMAT "%-25.25s %-15u %-15u \n"
1762 struct sorcery_memory_cached_object *cached = obj;
1763 struct print_object_details *details = arg;
1764 int seconds_until_expire = 0, seconds_until_stale = 0;
1765
1766 if (details->cache->object_lifetime_maximum) {
1767 seconds_until_expire = ast_tvdiff_ms(ast_tvadd(cached->created, ast_samp2tv(details->cache->object_lifetime_maximum, 1)), ast_tvnow()) / 1000;
1768 }
1769 if (details->cache->object_lifetime_stale) {
1770 seconds_until_stale = ast_tvdiff_ms(ast_tvadd(cached->created, ast_samp2tv(details->cache->object_lifetime_stale, 1)), ast_tvnow()) / 1000;
1771 }
1772
1773 ast_cli(details->a->fd, FORMAT, ast_sorcery_object_get_id(cached->object), MAX(seconds_until_stale, 0), MAX(seconds_until_expire, 0));
1774
1775 return CMP_MATCH;
1776#undef FORMAT
1777}
1778
1779/*!
1780 * \internal
1781 * \brief CLI command implementation for 'sorcery memory cache dump'
1782 */
1783static char *sorcery_memory_cache_dump(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1784{
1785#define FORMAT "%-25.25s %-15.15s %-15.15s \n"
1787 struct print_object_details details;
1788
1789 switch (cmd) {
1790 case CLI_INIT:
1791 e->command = "sorcery memory cache dump";
1792 e->usage =
1793 "Usage: sorcery memory cache dump <name>\n"
1794 " Dump a list of the objects within the cache, listed by object identifier.\n";
1795 return NULL;
1796 case CLI_GENERATE:
1797 if (a->pos == 4) {
1798 return sorcery_memory_cache_complete_name(a->word, a->n);
1799 } else {
1800 return NULL;
1801 }
1802 }
1803
1804 if (a->argc != 5) {
1805 return CLI_SHOWUSAGE;
1806 }
1807
1808 cache = ao2_find(caches, a->argv[4], OBJ_SEARCH_KEY);
1809 if (!cache) {
1810 ast_cli(a->fd, "Specified sorcery memory cache '%s' does not exist\n", a->argv[4]);
1811 return CLI_FAILURE;
1812 }
1813
1814 details.cache = cache;
1815 details.a = a;
1816
1817 ast_cli(a->fd, "Dumping sorcery memory cache '%s':\n", cache->name);
1818 if (!cache->object_lifetime_stale) {
1819 ast_cli(a->fd, " * Staleness is not enabled - objects will not go stale\n");
1820 }
1821 if (!cache->object_lifetime_maximum) {
1822 ast_cli(a->fd, " * Object lifetime is not enabled - objects will not expire\n");
1823 }
1824 ast_cli(a->fd, FORMAT, "Object Name", "Stale In", "Expires In");
1825 ast_cli(a->fd, FORMAT, "-------------------------", "---------------", "---------------");
1827 ast_cli(a->fd, FORMAT, "-------------------------", "---------------", "---------------");
1828 ast_cli(a->fd, "Total number of objects cached: %d\n", ao2_container_count(cache->objects));
1829
1830 ao2_ref(cache, -1);
1831
1832 return CLI_SUCCESS;
1833#undef FORMAT
1834}
1835
1836/*!
1837 * \internal
1838 * \brief CLI tab completion for cached object names
1839 */
1840static char *sorcery_memory_cache_complete_object_name(const char *cache_name, const char *word, int state)
1841{
1843 struct sorcery_memory_cached_object *cached;
1844 struct ao2_iterator it_cached;
1845 int wordlen = strlen(word);
1846 int which = 0;
1847 char *result = NULL;
1848
1849 cache = ao2_find(caches, cache_name, OBJ_SEARCH_KEY);
1850 if (!cache) {
1851 return NULL;
1852 }
1853
1854 it_cached = ao2_iterator_init(cache->objects, 0);
1855 while ((cached = ao2_iterator_next(&it_cached))) {
1856 if (!strncasecmp(word, ast_sorcery_object_get_id(cached->object), wordlen)
1857 && ++which > state) {
1859 }
1860 ao2_ref(cached, -1);
1861 if (result) {
1862 break;
1863 }
1864 }
1865 ao2_iterator_destroy(&it_cached);
1866
1867 ao2_ref(cache, -1);
1868
1869 return result;
1870}
1871
1872/*!
1873 * \internal
1874 * \brief CLI command implementation for 'sorcery memory cache expire'
1875 */
1876static char *sorcery_memory_cache_expire(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1877{
1879
1880 switch (cmd) {
1881 case CLI_INIT:
1882 e->command = "sorcery memory cache expire";
1883 e->usage =
1884 "Usage: sorcery memory cache expire <cache name> [object name]\n"
1885 " Expire a specific object or ALL objects within a sorcery memory cache.\n";
1886 return NULL;
1887 case CLI_GENERATE:
1888 if (a->pos == 4) {
1889 return sorcery_memory_cache_complete_name(a->word, a->n);
1890 } else if (a->pos == 5) {
1891 return sorcery_memory_cache_complete_object_name(a->argv[4], a->word, a->n);
1892 } else {
1893 return NULL;
1894 }
1895 }
1896
1897 if (a->argc < 5 || a->argc > 6) {
1898 return CLI_SHOWUSAGE;
1899 }
1900
1901 cache = ao2_find(caches, a->argv[4], OBJ_SEARCH_KEY);
1902 if (!cache) {
1903 ast_cli(a->fd, "Specified sorcery memory cache '%s' does not exist\n", a->argv[4]);
1904 return CLI_FAILURE;
1905 }
1906
1907 ao2_wrlock(cache->objects);
1908 if (a->argc == 5) {
1910 ast_cli(a->fd, "All objects have been removed from cache '%s'\n", a->argv[4]);
1911 } else {
1912 if (cache->full_backend_cache) {
1913 ast_cli(a->fd, "Due to full backend caching per-object expiration is not available on cache '%s'\n", a->argv[4]);
1914 } else if (!remove_from_cache(cache, a->argv[5], 1)) {
1915 ast_cli(a->fd, "Successfully expired object '%s' from cache '%s'\n", a->argv[5], a->argv[4]);
1916 } else {
1917 ast_cli(a->fd, "Object '%s' was not expired from cache '%s' as it was not found\n", a->argv[5],
1918 a->argv[4]);
1919 }
1920 }
1921 ao2_unlock(cache->objects);
1922
1923 ao2_ref(cache, -1);
1924
1925 return CLI_SUCCESS;
1926}
1927
1928/*!
1929 * \internal
1930 * \brief CLI command implementation for 'sorcery memory cache stale'
1931 */
1932static char *sorcery_memory_cache_stale(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
1933{
1935 int reload = 0;
1936
1937 switch (cmd) {
1938 case CLI_INIT:
1939 e->command = "sorcery memory cache stale";
1940 e->usage =
1941 "Usage: sorcery memory cache stale <cache name> [object name [reload]]\n"
1942 " Mark a specific object or ALL objects as stale in a sorcery memory cache.\n"
1943 " If \"reload\" is specified, then the object is marked stale and immediately\n"
1944 " retrieved from backend storage to repopulate the cache\n";
1945 return NULL;
1946 case CLI_GENERATE:
1947 if (a->pos == 4) {
1948 return sorcery_memory_cache_complete_name(a->word, a->n);
1949 } else if (a->pos == 5) {
1950 return sorcery_memory_cache_complete_object_name(a->argv[4], a->word, a->n);
1951 } else if (a->pos == 6) {
1952 static const char * const completions[] = { "reload", NULL };
1953 return ast_cli_complete(a->word, completions, a->n);
1954 } else {
1955 return NULL;
1956 }
1957 }
1958
1959 if (a->argc < 5 || a->argc > 7) {
1960 return CLI_SHOWUSAGE;
1961 }
1962
1963 if (a->argc == 7) {
1964 if (!strcasecmp(a->argv[6], "reload")) {
1965 reload = 1;
1966 } else {
1967 return CLI_SHOWUSAGE;
1968 }
1969 }
1970
1971 cache = ao2_find(caches, a->argv[4], OBJ_SEARCH_KEY);
1972 if (!cache) {
1973 ast_cli(a->fd, "Specified sorcery memory cache '%s' does not exist\n", a->argv[4]);
1974 return CLI_FAILURE;
1975 }
1976
1977 if (!cache->object_lifetime_stale) {
1978 ast_cli(a->fd, "Specified sorcery memory cache '%s' does not have staleness enabled\n", a->argv[4]);
1979 ao2_ref(cache, -1);
1980 return CLI_FAILURE;
1981 }
1982
1983 ao2_rdlock(cache->objects);
1984 if (a->argc == 5) {
1986 ast_cli(a->fd, "Marked all objects in sorcery memory cache '%s' as stale\n", a->argv[4]);
1987 } else {
1988 if (!mark_object_as_stale_in_cache(cache, a->argv[5])) {
1989 ast_cli(a->fd, "Successfully marked object '%s' in memory cache '%s' as stale\n",
1990 a->argv[5], a->argv[4]);
1991 if (reload) {
1992 struct sorcery_memory_cached_object *cached;
1993
1994 cached = ao2_find(cache->objects, a->argv[5], OBJ_SEARCH_KEY | OBJ_NOLOCK);
1995 if (cached) {
1996 memory_cache_stale_update_object(cache->sorcery, cache, cached);
1997 ao2_ref(cached, -1);
1998 }
1999 }
2000 } else {
2001 ast_cli(a->fd, "Object '%s' in sorcery memory cache '%s' could not be marked as stale as it was not found\n",
2002 a->argv[5], a->argv[4]);
2003 }
2004 }
2005 ao2_unlock(cache->objects);
2006
2007 ao2_ref(cache, -1);
2008
2009 return CLI_SUCCESS;
2010}
2011
2012/*!
2013 * \internal
2014 * \brief CLI command implementation for 'sorcery memory cache populate'
2015 */
2016static char *sorcery_memory_cache_populate(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
2017{
2019
2020 switch (cmd) {
2021 case CLI_INIT:
2022 e->command = "sorcery memory cache populate";
2023 e->usage =
2024 "Usage: sorcery memory cache populate <cache name>\n"
2025 " Expire all objects in the cache and populate it with ALL objects from backend.\n";
2026 return NULL;
2027 case CLI_GENERATE:
2028 if (a->pos == 4) {
2029 return sorcery_memory_cache_complete_name(a->word, a->n);
2030 } else {
2031 return NULL;
2032 }
2033 }
2034
2035 if (a->argc != 5) {
2036 return CLI_SHOWUSAGE;
2037 }
2038
2039 cache = ao2_find(caches, a->argv[4], OBJ_SEARCH_KEY);
2040 if (!cache) {
2041 ast_cli(a->fd, "Specified sorcery memory cache '%s' does not exist\n", a->argv[4]);
2042 return CLI_FAILURE;
2043 }
2044
2045 if (!cache->full_backend_cache) {
2046 ast_cli(a->fd, "Specified sorcery memory cache '%s' does not have full backend caching enabled\n", a->argv[4]);
2047 ao2_ref(cache, -1);
2048 return CLI_FAILURE;
2049 }
2050
2051 ao2_wrlock(cache->objects);
2052 if (!cache->sorcery) {
2053 ast_cli(a->fd, "Specified sorcery memory cache '%s' is no longer active\n", a->argv[4]);
2054 ao2_unlock(cache->objects);
2055 ao2_ref(cache, -1);
2056 return CLI_FAILURE;
2057 }
2058
2060 memory_cache_populate(cache->sorcery, cache->object_type, cache);
2061
2062 ast_cli(a->fd, "Specified sorcery memory cache '%s' has been populated with '%d' objects from the backend\n",
2063 a->argv[4], ao2_container_count(cache->objects));
2064
2065 ao2_unlock(cache->objects);
2066
2067 ao2_ref(cache, -1);
2068
2069 return CLI_SUCCESS;
2070}
2071
2073 AST_CLI_DEFINE(sorcery_memory_cache_show, "Show sorcery memory cache information"),
2074 AST_CLI_DEFINE(sorcery_memory_cache_dump, "Dump all objects within a sorcery memory cache"),
2075 AST_CLI_DEFINE(sorcery_memory_cache_expire, "Expire a specific object or ALL objects within a sorcery memory cache"),
2076 AST_CLI_DEFINE(sorcery_memory_cache_stale, "Mark a specific object or ALL objects as stale within a sorcery memory cache"),
2077 AST_CLI_DEFINE(sorcery_memory_cache_populate, "Clear and populate the sorcery memory cache with objects from the backend"),
2078};
2079
2080/*!
2081 * \internal
2082 * \brief AMI command implementation for 'SorceryMemoryCacheExpireObject'
2083 */
2084static int sorcery_memory_cache_ami_expire_object(struct mansession *s, const struct message *m)
2085{
2086 const char *cache_name = astman_get_header(m, "Cache");
2087 const char *object_name = astman_get_header(m, "Object");
2089 int res;
2090
2091 if (ast_strlen_zero(cache_name)) {
2092 astman_send_error(s, m, "SorceryMemoryCacheExpireObject requires that a cache name be provided.\n");
2093 return 0;
2094 } else if (ast_strlen_zero(object_name)) {
2095 astman_send_error(s, m, "SorceryMemoryCacheExpireObject requires that an object name be provided\n");
2096 return 0;
2097 }
2098
2099 cache = ao2_find(caches, cache_name, OBJ_SEARCH_KEY);
2100 if (!cache) {
2101 astman_send_error(s, m, "The provided cache does not exist\n");
2102 return 0;
2103 }
2104
2105 ao2_wrlock(cache->objects);
2106 if (cache->full_backend_cache) {
2107 res = 1;
2108 } else {
2109 res = remove_from_cache(cache, object_name, 1);
2110 }
2111 ao2_unlock(cache->objects);
2112
2113 ao2_ref(cache, -1);
2114
2115 if (res == 1) {
2116 astman_send_error(s, m, "Due to full backend caching per-object expiration is not available, consider using SorceryMemoryCachePopulate or SorceryMemoryCacheExpire instead\n");
2117 } else if (!res) {
2118 astman_send_ack(s, m, "The provided object was expired from the cache\n");
2119 } else {
2120 astman_send_error(s, m, "The provided object could not be expired from the cache\n");
2121 }
2122
2123 return 0;
2124}
2125
2126/*!
2127 * \internal
2128 * \brief AMI command implementation for 'SorceryMemoryCacheExpire'
2129 */
2130static int sorcery_memory_cache_ami_expire(struct mansession *s, const struct message *m)
2131{
2132 const char *cache_name = astman_get_header(m, "Cache");
2134
2135 if (ast_strlen_zero(cache_name)) {
2136 astman_send_error(s, m, "SorceryMemoryCacheExpire requires that a cache name be provided.\n");
2137 return 0;
2138 }
2139
2140 cache = ao2_find(caches, cache_name, OBJ_SEARCH_KEY);
2141 if (!cache) {
2142 astman_send_error(s, m, "The provided cache does not exist\n");
2143 return 0;
2144 }
2145
2146 ao2_wrlock(cache->objects);
2148 ao2_unlock(cache->objects);
2149
2150 ao2_ref(cache, -1);
2151
2152 astman_send_ack(s, m, "All objects were expired from the cache\n");
2153
2154 return 0;
2155}
2156
2157/*!
2158 * \internal
2159 * \brief AMI command implementation for 'SorceryMemoryCacheStaleObject'
2160 */
2161static int sorcery_memory_cache_ami_stale_object(struct mansession *s, const struct message *m)
2162{
2163 const char *cache_name = astman_get_header(m, "Cache");
2164 const char *object_name = astman_get_header(m, "Object");
2165 const char *reload = astman_get_header(m, "Reload");
2167 int res;
2168
2169 if (ast_strlen_zero(cache_name)) {
2170 astman_send_error(s, m, "SorceryMemoryCacheStaleObject requires that a cache name be provided.\n");
2171 return 0;
2172 } else if (ast_strlen_zero(object_name)) {
2173 astman_send_error(s, m, "SorceryMemoryCacheStaleObject requires that an object name be provided\n");
2174 return 0;
2175 }
2176
2177 cache = ao2_find(caches, cache_name, OBJ_SEARCH_KEY);
2178 if (!cache) {
2179 astman_send_error(s, m, "The provided cache does not exist\n");
2180 return 0;
2181 }
2182
2183 ao2_rdlock(cache->objects);
2184
2185 res = mark_object_as_stale_in_cache(cache, object_name);
2186
2187 if (ast_true(reload)) {
2188 struct sorcery_memory_cached_object *cached;
2189
2190 cached = ao2_find(cache->objects, object_name, OBJ_SEARCH_KEY | OBJ_NOLOCK);
2191 if (cached) {
2192 memory_cache_stale_update_object(cache->sorcery, cache, cached);
2193 ao2_ref(cached, -1);
2194 }
2195 }
2196
2197 ao2_unlock(cache->objects);
2198
2199 ao2_ref(cache, -1);
2200
2201 if (!res) {
2202 astman_send_ack(s, m, "The provided object was marked as stale in the cache\n");
2203 } else {
2204 astman_send_error(s, m, "The provided object could not be marked as stale in the cache\n");
2205 }
2206
2207 return 0;
2208}
2209
2210/*!
2211 * \internal
2212 * \brief AMI command implementation for 'SorceryMemoryCacheStale'
2213 */
2214static int sorcery_memory_cache_ami_stale(struct mansession *s, const struct message *m)
2215{
2216 const char *cache_name = astman_get_header(m, "Cache");
2218
2219 if (ast_strlen_zero(cache_name)) {
2220 astman_send_error(s, m, "SorceryMemoryCacheStale requires that a cache name be provided.\n");
2221 return 0;
2222 }
2223
2224 cache = ao2_find(caches, cache_name, OBJ_SEARCH_KEY);
2225 if (!cache) {
2226 astman_send_error(s, m, "The provided cache does not exist\n");
2227 return 0;
2228 }
2229
2230 ao2_rdlock(cache->objects);
2232 ao2_unlock(cache->objects);
2233
2234 ao2_ref(cache, -1);
2235
2236 astman_send_ack(s, m, "All objects were marked as stale in the cache\n");
2237
2238 return 0;
2239}
2240
2241/*!
2242 * \internal
2243 * \brief AMI command implementation for 'SorceryMemoryCachePopulate'
2244 */
2245static int sorcery_memory_cache_ami_populate(struct mansession *s, const struct message *m)
2246{
2247 const char *cache_name = astman_get_header(m, "Cache");
2249
2250 if (ast_strlen_zero(cache_name)) {
2251 astman_send_error(s, m, "SorceryMemoryCachePopulate requires that a cache name be provided.\n");
2252 return 0;
2253 }
2254
2255 cache = ao2_find(caches, cache_name, OBJ_SEARCH_KEY);
2256 if (!cache) {
2257 astman_send_error(s, m, "The provided cache does not exist\n");
2258 return 0;
2259 }
2260
2261 if (!cache->full_backend_cache) {
2262 astman_send_error(s, m, "The provided cache does not have full backend caching enabled\n");
2263 ao2_ref(cache, -1);
2264 return 0;
2265 }
2266
2267 ao2_wrlock(cache->objects);
2268 if (!cache->sorcery) {
2269 astman_send_error(s, m, "The provided cache is no longer active\n");
2270 ao2_unlock(cache->objects);
2271 ao2_ref(cache, -1);
2272 return 0;
2273 }
2274
2276 memory_cache_populate(cache->sorcery, cache->object_type, cache);
2277
2278 ao2_unlock(cache->objects);
2279
2280 ao2_ref(cache, -1);
2281
2282 astman_send_ack(s, m, "Cache has been expired and populated\n");
2283
2284 return 0;
2285}
2286
2287#ifdef TEST_FRAMEWORK
2288
2289/*! \brief Dummy sorcery object */
2290struct test_sorcery_object {
2291 SORCERY_OBJECT(details);
2292};
2293
2294/*!
2295 * \internal
2296 * \brief Allocator for test object
2297 *
2298 * \param id The identifier for the object
2299 *
2300 * \retval non-NULL success
2301 * \retval NULL failure
2302 */
2303static void *test_sorcery_object_alloc(const char *id)
2304{
2305 return ast_sorcery_generic_alloc(sizeof(struct test_sorcery_object), NULL);
2306}
2307
2308/*!
2309 * \internal
2310 * \brief Allocator for test sorcery instance
2311 *
2312 * \retval non-NULL success
2313 * \retval NULL failure
2314 */
2315static struct ast_sorcery *alloc_and_initialize_sorcery(void)
2316{
2317 struct ast_sorcery *sorcery;
2318
2319 if (!(sorcery = ast_sorcery_open())) {
2320 return NULL;
2321 }
2322
2323 if ((ast_sorcery_apply_default(sorcery, "test", "memory", NULL) != AST_SORCERY_APPLY_SUCCESS) ||
2326 return NULL;
2327 }
2328
2329 return sorcery;
2330}
2331
2332AST_TEST_DEFINE(open_with_valid_options)
2333{
2334 int res = AST_TEST_PASS;
2336
2337 switch (cmd) {
2338 case TEST_INIT:
2339 info->name = "open_with_valid_options";
2340 info->category = "/res/res_sorcery_memory_cache/";
2341 info->summary = "Attempt to create sorcery memory caches using valid options";
2342 info->description = "This test performs the following:\n"
2343 "\t* Creates a memory cache with default configuration\n"
2344 "\t* Creates a memory cache with a maximum object count of 10 and verifies it\n"
2345 "\t* Creates a memory cache with a maximum object lifetime of 60 and verifies it\n"
2346 "\t* Creates a memory cache with a stale object lifetime of 90 and verifies it";
2347 return AST_TEST_NOT_RUN;
2348 case TEST_EXECUTE:
2349 break;
2350 }
2351
2353 if (!cache) {
2354 ast_test_status_update(test, "Failed to create a sorcery memory cache using default configuration\n");
2355 res = AST_TEST_FAIL;
2356 } else {
2358 }
2359
2360 cache = sorcery_memory_cache_open("maximum_objects=10");
2361 if (!cache) {
2362 ast_test_status_update(test, "Failed to create a sorcery memory cache with a maximum object count of 10\n");
2363 res = AST_TEST_FAIL;
2364 } else {
2365 if (cache->maximum_objects != 10) {
2366 ast_test_status_update(test, "Created a sorcery memory cache with a maximum object count of 10 but it has '%u'\n",
2367 cache->maximum_objects);
2368 }
2370 }
2371
2372 cache = sorcery_memory_cache_open("object_lifetime_maximum=60");
2373 if (!cache) {
2374 ast_test_status_update(test, "Failed to create a sorcery memory cache with a maximum object lifetime of 60\n");
2375 res = AST_TEST_FAIL;
2376 } else {
2377 if (cache->object_lifetime_maximum != 60) {
2378 ast_test_status_update(test, "Created a sorcery memory cache with a maximum object lifetime of 60 but it has '%u'\n",
2379 cache->object_lifetime_maximum);
2380 }
2382 }
2383
2384 cache = sorcery_memory_cache_open("object_lifetime_stale=90");
2385 if (!cache) {
2386 ast_test_status_update(test, "Failed to create a sorcery memory cache with a stale object lifetime of 90\n");
2387 res = AST_TEST_FAIL;
2388 } else {
2389 if (cache->object_lifetime_stale != 90) {
2390 ast_test_status_update(test, "Created a sorcery memory cache with a stale object lifetime of 90 but it has '%u'\n",
2391 cache->object_lifetime_stale);
2392 }
2394 }
2395
2396
2397 return res;
2398}
2399
2400AST_TEST_DEFINE(open_with_invalid_options)
2401{
2402 int res = AST_TEST_PASS;
2404
2405 switch (cmd) {
2406 case TEST_INIT:
2407 info->name = "open_with_invalid_options";
2408 info->category = "/res/res_sorcery_memory_cache/";
2409 info->summary = "Attempt to create sorcery memory caches using invalid options";
2410 info->description = "This test attempts to perform the following:\n"
2411 "\t* Create a memory cache with an empty name\n"
2412 "\t* Create a memory cache with a maximum object count of -1\n"
2413 "\t* Create a memory cache with a maximum object count of toast\n"
2414 "\t* Create a memory cache with a maximum object lifetime of -1\n"
2415 "\t* Create a memory cache with a maximum object lifetime of toast\n"
2416 "\t* Create a memory cache with a stale object lifetime of -1\n"
2417 "\t* Create a memory cache with a stale object lifetime of toast";
2418 return AST_TEST_NOT_RUN;
2419 case TEST_EXECUTE:
2420 break;
2421 }
2422
2424 if (cache) {
2425 ast_test_status_update(test, "Created a sorcery memory cache with an empty name\n");
2427 res = AST_TEST_FAIL;
2428 }
2429
2430 cache = sorcery_memory_cache_open("maximum_objects=-1");
2431 if (cache) {
2432 ast_test_status_update(test, "Created a sorcery memory cache with a maximum object count of -1\n");
2434 res = AST_TEST_FAIL;
2435 }
2436
2437 cache = sorcery_memory_cache_open("maximum_objects=toast");
2438 if (cache) {
2439 ast_test_status_update(test, "Created a sorcery memory cache with a maximum object count of toast\n");
2441 res = AST_TEST_FAIL;
2442 }
2443
2444 cache = sorcery_memory_cache_open("object_lifetime_maximum=-1");
2445 if (cache) {
2446 ast_test_status_update(test, "Created a sorcery memory cache with an object lifetime maximum of -1\n");
2448 res = AST_TEST_FAIL;
2449 }
2450
2451 cache = sorcery_memory_cache_open("object_lifetime_maximum=toast");
2452 if (cache) {
2453 ast_test_status_update(test, "Created a sorcery memory cache with an object lifetime maximum of toast\n");
2455 res = AST_TEST_FAIL;
2456 }
2457
2458 cache = sorcery_memory_cache_open("object_lifetime_stale=-1");
2459 if (cache) {
2460 ast_test_status_update(test, "Created a sorcery memory cache with a stale object lifetime of -1\n");
2462 res = AST_TEST_FAIL;
2463 }
2464
2465 cache = sorcery_memory_cache_open("object_lifetime_stale=toast");
2466 if (cache) {
2467 ast_test_status_update(test, "Created a sorcery memory cache with a stale object lifetime of toast\n");
2469 res = AST_TEST_FAIL;
2470 }
2471
2473 if (cache) {
2474 ast_test_status_update(test, "Created a sorcery memory cache with an invalid configuration option 'tacos'\n");
2476 res = AST_TEST_FAIL;
2477 }
2478
2479 return res;
2480}
2481
2482AST_TEST_DEFINE(create_and_retrieve)
2483{
2484 int res = AST_TEST_FAIL;
2485 struct ast_sorcery *sorcery = NULL;
2487 RAII_VAR(void *, object, NULL, ao2_cleanup);
2488 RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
2489
2490 switch (cmd) {
2491 case TEST_INIT:
2492 info->name = "create";
2493 info->category = "/res/res_sorcery_memory_cache/";
2494 info->summary = "Attempt to create an object in the cache";
2495 info->description = "This test performs the following:\n"
2496 "\t* Creates a memory cache with default options\n"
2497 "\t* Creates a sorcery instance with a test object\n"
2498 "\t* Creates a test object with an id of test\n"
2499 "\t* Pushes the test object into the memory cache\n"
2500 "\t* Confirms that the test object is in the cache";
2501 return AST_TEST_NOT_RUN;
2502 case TEST_EXECUTE:
2503 break;
2504 }
2505
2507 if (!cache) {
2508 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
2509 goto cleanup;
2510 }
2511
2512 if (ao2_container_count(cache->objects)) {
2513 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
2514 goto cleanup;
2515 }
2516
2518 if (!sorcery) {
2519 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
2520 goto cleanup;
2521 }
2522
2523 object = ast_sorcery_alloc(sorcery, "test", "test");
2524 if (!object) {
2525 ast_test_status_update(test, "Failed to allocate a test object\n");
2526 goto cleanup;
2527 }
2528
2530
2531 if (!ao2_container_count(cache->objects)) {
2532 ast_test_status_update(test, "Added test object to memory cache but cache remains empty\n");
2533 goto cleanup;
2534 }
2535
2536 cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
2537 if (!cached_object) {
2538 ast_test_status_update(test, "Object placed into memory cache could not be retrieved\n");
2539 goto cleanup;
2540 }
2541
2542 if (cached_object != object) {
2543 ast_test_status_update(test, "Object retrieved from memory cached is not the one we cached\n");
2544 goto cleanup;
2545 }
2546
2547 res = AST_TEST_PASS;
2548
2549cleanup:
2550 if (cache) {
2552 }
2553 if (sorcery) {
2555 }
2556
2557 return res;
2558}
2559
2561{
2562 int res = AST_TEST_FAIL;
2563 struct ast_sorcery *sorcery = NULL;
2565 RAII_VAR(void *, original_object, NULL, ao2_cleanup);
2566 RAII_VAR(void *, updated_object, NULL, ao2_cleanup);
2567 RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
2568
2569 switch (cmd) {
2570 case TEST_INIT:
2571 info->name = "create";
2572 info->category = "/res/res_sorcery_memory_cache/";
2573 info->summary = "Attempt to create and then update an object in the cache";
2574 info->description = "This test performs the following:\n"
2575 "\t* Creates a memory cache with default options\n"
2576 "\t* Creates a sorcery instance with a test object\n"
2577 "\t* Creates a test object with an id of test\n"
2578 "\t* Pushes the test object into the memory cache\n"
2579 "\t* Confirms that the test object is in the cache\n"
2580 "\t* Creates a new test object with the same id of test\n"
2581 "\t* Pushes the new test object into the memory cache\n"
2582 "\t* Confirms that the new test object has replaced the old one";
2583 return AST_TEST_NOT_RUN;
2584 case TEST_EXECUTE:
2585 break;
2586 }
2587
2589 if (!cache) {
2590 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
2591 goto cleanup;
2592 }
2593
2594 if (ao2_container_count(cache->objects)) {
2595 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
2596 goto cleanup;
2597 }
2598
2600 if (!sorcery) {
2601 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
2602 goto cleanup;
2603 }
2604
2605 original_object = ast_sorcery_alloc(sorcery, "test", "test");
2606 if (!original_object) {
2607 ast_test_status_update(test, "Failed to allocate a test object\n");
2608 goto cleanup;
2609 }
2610
2611 sorcery_memory_cache_create(sorcery, cache, original_object);
2612
2613 updated_object = ast_sorcery_alloc(sorcery, "test", "test");
2614 if (!updated_object) {
2615 ast_test_status_update(test, "Failed to allocate an updated test object\n");
2616 goto cleanup;
2617 }
2618
2619 sorcery_memory_cache_create(sorcery, cache, updated_object);
2620
2621 if (ao2_container_count(cache->objects) != 1) {
2622 ast_test_status_update(test, "Added updated test object to memory cache but cache now contains %d objects instead of 1\n",
2623 ao2_container_count(cache->objects));
2624 goto cleanup;
2625 }
2626
2627 cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
2628 if (!cached_object) {
2629 ast_test_status_update(test, "Updated object placed into memory cache could not be retrieved\n");
2630 goto cleanup;
2631 }
2632
2633 if (cached_object == original_object) {
2634 ast_test_status_update(test, "Updated object placed into memory cache but old one is being retrieved\n");
2635 goto cleanup;
2636 } else if (cached_object != updated_object) {
2637 ast_test_status_update(test, "Updated object placed into memory cache but different one is being retrieved\n");
2638 goto cleanup;
2639 }
2640
2641 res = AST_TEST_PASS;
2642
2643cleanup:
2644 if (cache) {
2646 }
2647 if (sorcery) {
2649 }
2650
2651 return res;
2652}
2653
2654AST_TEST_DEFINE(delete)
2655{
2656 int res = AST_TEST_FAIL;
2657 struct ast_sorcery *sorcery = NULL;
2659 RAII_VAR(void *, object, NULL, ao2_cleanup);
2660 RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
2661
2662 switch (cmd) {
2663 case TEST_INIT:
2664 info->name = "delete";
2665 info->category = "/res/res_sorcery_memory_cache/";
2666 info->summary = "Attempt to create and then delete an object in the cache";
2667 info->description = "This test performs the following:\n"
2668 "\t* Creates a memory cache with default options\n"
2669 "\t* Creates a sorcery instance with a test object\n"
2670 "\t* Creates a test object with an id of test\n"
2671 "\t* Pushes the test object into the memory cache\n"
2672 "\t* Confirms that the test object is in the cache\n"
2673 "\t* Deletes the test object from the cache\n"
2674 "\t* Confirms that the test object is no longer in the cache";
2675 return AST_TEST_NOT_RUN;
2676 case TEST_EXECUTE:
2677 break;
2678 }
2679
2681 if (!cache) {
2682 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
2683 goto cleanup;
2684 }
2685
2686 if (ao2_container_count(cache->objects)) {
2687 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
2688 goto cleanup;
2689 }
2690
2692 if (!sorcery) {
2693 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
2694 goto cleanup;
2695 }
2696
2697 object = ast_sorcery_alloc(sorcery, "test", "test");
2698 if (!object) {
2699 ast_test_status_update(test, "Failed to allocate a test object\n");
2700 goto cleanup;
2701 }
2702
2704
2705 if (!ao2_container_count(cache->objects)) {
2706 ast_test_status_update(test, "Added test object to memory cache but cache contains no objects\n");
2707 goto cleanup;
2708 }
2709
2710 cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
2711 if (!cached_object) {
2712 ast_test_status_update(test, "Test object placed into memory cache could not be retrieved\n");
2713 goto cleanup;
2714 }
2715
2716 ao2_ref(cached_object, -1);
2717 cached_object = NULL;
2718
2720
2721 cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", "test");
2722 if (cached_object) {
2723 ast_test_status_update(test, "Test object deleted from memory cache can still be retrieved\n");
2724 goto cleanup;
2725 }
2726
2727 res = AST_TEST_PASS;
2728
2729cleanup:
2730 if (cache) {
2732 }
2733 if (sorcery) {
2735 }
2736
2737 return res;
2738}
2739
2740static int check_cache_content(struct ast_test *test, struct ast_sorcery *sorcery, struct sorcery_memory_cache *cache,
2741 const char **in_cache, size_t num_in_cache, const char **not_in_cache, size_t num_not_in_cache)
2742{
2743 int i;
2744 int res = 0;
2745 RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
2746
2747 for (i = 0; i < num_in_cache; ++i) {
2748 cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", in_cache[i]);
2749 if (!cached_object) {
2750 ast_test_status_update(test, "Failed to retrieve '%s' object from the cache\n",
2751 in_cache[i]);
2752 res = -1;
2753 }
2754 ao2_ref(cached_object, -1);
2755 }
2756
2757 for (i = 0; i < num_not_in_cache; ++i) {
2758 cached_object = sorcery_memory_cache_retrieve_id(sorcery, cache, "test", not_in_cache[i]);
2759 if (cached_object) {
2760 ast_test_status_update(test, "Retrieved '%s' object from the cache unexpectedly\n",
2761 not_in_cache[i]);
2762 ao2_ref(cached_object, -1);
2763 res = -1;
2764 }
2765 }
2766
2767 return res;
2768}
2769
2771{
2772 int res = AST_TEST_FAIL;
2773 struct ast_sorcery *sorcery = NULL;
2775 RAII_VAR(void *, alice, NULL, ao2_cleanup);
2776 RAII_VAR(void *, bob, NULL, ao2_cleanup);
2777 RAII_VAR(void *, charlie, NULL, ao2_cleanup);
2778 RAII_VAR(void *, cached_object, NULL, ao2_cleanup);
2779 const char *in_cache[2];
2780 const char *not_in_cache[2];
2781
2782 switch (cmd) {
2783 case TEST_INIT:
2784 info->name = "maximum_objects";
2785 info->category = "/res/res_sorcery_memory_cache/";
2786 info->summary = "Ensure that the 'maximum_objects' option works as expected";
2787 info->description = "This test performs the following:\n"
2788 "\t* Creates a memory cache with maximum_objects=2\n"
2789 "\t* Creates a sorcery instance\n"
2790 "\t* Creates a three test objects: alice, bob, charlie, and david\n"
2791 "\t* Pushes alice and bob into the memory cache\n"
2792 "\t* Confirms that alice and bob are in the memory cache\n"
2793 "\t* Pushes charlie into the memory cache\n"
2794 "\t* Confirms that bob and charlie are in the memory cache\n"
2795 "\t* Deletes charlie from the memory cache\n"
2796 "\t* Confirms that only bob is in the memory cache\n"
2797 "\t* Pushes alice into the memory cache\n"
2798 "\t* Confirms that bob and alice are in the memory cache";
2799 return AST_TEST_NOT_RUN;
2800 case TEST_EXECUTE:
2801 break;
2802 }
2803
2804 cache = sorcery_memory_cache_open("maximum_objects=2");
2805 if (!cache) {
2806 ast_test_status_update(test, "Failed to create a sorcery memory cache with maximum_objects=2\n");
2807 goto cleanup;
2808 }
2809
2810 if (ao2_container_count(cache->objects)) {
2811 ast_test_status_update(test, "Memory cache contains cached objects before we added one\n");
2812 goto cleanup;
2813 }
2814
2816 if (!sorcery) {
2817 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
2818 goto cleanup;
2819 }
2820
2821 alice = ast_sorcery_alloc(sorcery, "test", "alice");
2822 bob = ast_sorcery_alloc(sorcery, "test", "bob");
2823 charlie = ast_sorcery_alloc(sorcery, "test", "charlie");
2824
2825 if (!alice || !bob || !charlie) {
2826 ast_test_status_update(test, "Failed to allocate sorcery object(s)\n");
2827 goto cleanup;
2828 }
2829
2831 in_cache[0] = "alice";
2832 in_cache[1] = NULL;
2833 not_in_cache[0] = "bob";
2834 not_in_cache[1] = "charlie";
2835 if (check_cache_content(test, sorcery, cache, in_cache, 1, not_in_cache, 2)) {
2836 goto cleanup;
2837 }
2838
2839 /* Delays are added to ensure that we are not adding cache entries within the
2840 * same microsecond
2841 */
2842 usleep(1000);
2843
2845 in_cache[0] = "alice";
2846 in_cache[1] = "bob";
2847 not_in_cache[0] = "charlie";
2848 not_in_cache[1] = NULL;
2849 if (check_cache_content(test, sorcery, cache, in_cache, 2, not_in_cache, 1)) {
2850 goto cleanup;
2851 }
2852
2853 usleep(1000);
2854
2856 in_cache[0] = "bob";
2857 in_cache[1] = "charlie";
2858 not_in_cache[0] = "alice";
2859 not_in_cache[1] = NULL;
2860 if (check_cache_content(test, sorcery, cache, in_cache, 2, not_in_cache, 1)) {
2861 goto cleanup;
2862 }
2863 usleep(1000);
2864
2866 in_cache[0] = "bob";
2867 in_cache[1] = NULL;
2868 not_in_cache[0] = "alice";
2869 not_in_cache[1] = "charlie";
2870 if (check_cache_content(test, sorcery, cache, in_cache, 1, not_in_cache, 2)) {
2871 goto cleanup;
2872 }
2873 usleep(1000);
2874
2876 in_cache[0] = "bob";
2877 in_cache[1] = "alice";
2878 not_in_cache[0] = "charlie";
2879 not_in_cache[1] = NULL;
2880 if (check_cache_content(test, sorcery, cache, in_cache, 2, not_in_cache, 1)) {
2881 goto cleanup;
2882 }
2883
2884 res = AST_TEST_PASS;
2885
2886cleanup:
2887 if (cache) {
2889 }
2890 if (sorcery) {
2892 }
2893
2894 return res;
2895}
2896
2897AST_TEST_DEFINE(expiration)
2898{
2899 int res = AST_TEST_FAIL;
2900 struct ast_sorcery *sorcery = NULL;
2902 int i;
2903
2904 switch (cmd) {
2905 case TEST_INIT:
2906 info->name = "expiration";
2907 info->category = "/res/res_sorcery_memory_cache/";
2908 info->summary = "Add objects to a cache configured with maximum lifetime, confirm they are removed";
2909 info->description = "This test performs the following:\n"
2910 "\t* Creates a memory cache with a maximum object lifetime of 5 seconds\n"
2911 "\t* Pushes 10 objects into the memory cache\n"
2912 "\t* Waits (up to) 10 seconds for expiration to occur\n"
2913 "\t* Confirms that the objects have been removed from the cache";
2914 return AST_TEST_NOT_RUN;
2915 case TEST_EXECUTE:
2916 break;
2917 }
2918
2919 cache = sorcery_memory_cache_open("object_lifetime_maximum=5");
2920 if (!cache) {
2921 ast_test_status_update(test, "Failed to create a sorcery memory cache using default options\n");
2922 goto cleanup;
2923 }
2924
2926 if (!sorcery) {
2927 ast_test_status_update(test, "Failed to create a test sorcery instance\n");
2928 goto cleanup;
2929 }
2930
2931 cache->cache_notify = 1;
2932 ast_mutex_init(&cache->lock);
2933 ast_cond_init(&cache->cond, NULL);
2934
2935 for (i = 0; i < 5; ++i) {
2936 char uuid[AST_UUID_STR_LEN];
2937 void *object;
2938
2939 object = ast_sorcery_alloc(sorcery, "test", ast_uuid_generate_str(uuid, sizeof(uuid)));
2940 if (!object) {
2941 ast_test_status_update(test, "Failed to allocate test object for expiration\n");
2942 goto cleanup;
2943 }
2944
2946
2947 ao2_ref(object, -1);
2948 }
2949
2950 ast_mutex_lock(&cache->lock);
2951 while (!cache->cache_completed) {
2952 struct timeval start = ast_tvnow();
2953 struct timespec end = {
2954 .tv_sec = start.tv_sec + 10,
2955 .tv_nsec = start.tv_usec * 1000,
2956 };
2957
2958 if (ast_cond_timedwait(&cache->cond, &cache->lock, &end) == ETIMEDOUT) {
2959 break;
2960 }
2961 }
2962 ast_mutex_unlock(&cache->lock);
2963
2964 if (ao2_container_count(cache->objects)) {
2965 ast_test_status_update(test, "Objects placed into the memory cache did not expire and get removed\n");
2966 goto cleanup;
2967 }
2968
2969 res = AST_TEST_PASS;
2970
2971cleanup:
2972 if (cache) {
2973 if (cache->cache_notify) {
2974 ast_cond_destroy(&cache->cond);
2975 ast_mutex_destroy(&cache->lock);
2976 }
2978 }
2979 if (sorcery) {
2981 }
2982
2983 return res;
2984}
2985
2986/*!
2987 * \brief Backend data that the mock sorcery wizard uses to create objects
2988 */
2989static struct backend_data {
2990 /*! An arbitrary data field */
2991 int salt;
2992 /*! Another arbitrary data field */
2993 int pepper;
2994 /*! Indicates whether the backend has data */
2995 int exists;
2996} *real_backend_data;
2997
2998/*!
2999 * \brief Sorcery object created based on backend data
3000 */
3001struct test_data {
3002 SORCERY_OBJECT(details);
3003 /*! Mirrors the backend data's salt field */
3004 int salt;
3005 /*! Mirrors the backend data's pepper field */
3006 int pepper;
3007};
3008
3009/*!
3010 * \brief Allocation callback for test_data sorcery object
3011 */
3012static void *test_data_alloc(const char *id) {
3013 return ast_sorcery_generic_alloc(sizeof(struct test_data), NULL);
3014}
3015
3016/*!
3017 * \brief Callback for retrieving sorcery object by ID
3018 *
3019 * The mock wizard uses the \ref real_backend_data in order to construct
3020 * objects. If the backend data is "nonexisent" then no object is returned.
3021 * Otherwise, an object is created that has the backend data's salt and
3022 * pepper values copied.
3023 *
3024 * \param sorcery The sorcery instance
3025 * \param data Unused
3026 * \param type The object type. Will always be "test".
3027 * \param id The object id. Will always be "test".
3028 *
3029 * \retval NULL Backend data does not exist
3030 * \retval non-NULL An object representing the backend data
3031 */
3032static void *mock_retrieve_id(const struct ast_sorcery *sorcery, void *data,
3033 const char *type, const char *id)
3034{
3035 struct test_data *b_data;
3036
3037 if (!real_backend_data->exists) {
3038 return NULL;
3039 }
3040
3041 b_data = ast_sorcery_alloc(sorcery, type, id);
3042 if (!b_data) {
3043 return NULL;
3044 }
3045
3046 b_data->salt = real_backend_data->salt;
3047 b_data->pepper = real_backend_data->pepper;
3048 return b_data;
3049}
3050
3051/*!
3052 * \brief Callback for retrieving multiple sorcery objects
3053 *
3054 * The mock wizard uses the \ref real_backend_data in order to construct
3055 * objects. If the backend data is "nonexisent" then no object is returned.
3056 * Otherwise, the number of objects matching the exists value will be returned.
3057 *
3058 * \param sorcery The sorcery instance
3059 * \param data Unused
3060 * \param type The object type. Will always be "test".
3061 * \param objects Container to place objects into.
3062 * \param fields Fields to search for.
3063 */
3064static void mock_retrieve_multiple(const struct ast_sorcery *sorcery, void *data,
3065 const char *type, struct ao2_container *objects, const struct ast_variable *fields)
3066{
3067 int i;
3068
3069 if (fields) {
3070 return;
3071 }
3072
3073 for (i = 0; i < real_backend_data->exists; ++i) {
3074 char uuid[AST_UUID_STR_LEN];
3075 struct test_data *b_data;
3076
3078 if (!b_data) {
3079 continue;
3080 }
3081
3082 b_data->salt = real_backend_data->salt;
3083 b_data->pepper = real_backend_data->pepper;
3084
3085 ao2_link(objects, b_data);
3086 ao2_ref(b_data, -1);
3087 }
3088}
3089
3090/*!
3091 * \brief A mock sorcery wizard used for the stale test
3092 */
3093static struct ast_sorcery_wizard mock_wizard = {
3094 .name = "mock",
3095 .retrieve_id = mock_retrieve_id,
3096 .retrieve_multiple = mock_retrieve_multiple,
3097};
3098
3099/*!
3100 * \brief Wait for the cache to be updated after a stale object is retrieved.
3101 *
3102 * Since the cache does not know what type of objects it is dealing with, and
3103 * since we do not have the internals of the cache, the only way to make this
3104 * determination is to continuously retrieve an object from the cache until
3105 * we retrieve a different object than we had previously retrieved.
3106 *
3107 * \param sorcery The sorcery instance
3108 * \param previous_object The object we had previously retrieved from the cache
3109 * \param[out] new_object The new object we retrieve from the cache
3110 *
3111 * \retval 0 Successfully retrieved a new object from the cache
3112 * \retval non-zero Failed to retrieve a new object from the cache
3113 */
3114static int wait_for_cache_update(const struct ast_sorcery *sorcery,
3115 void *previous_object, struct test_data **new_object)
3116{
3117 struct timeval start = ast_tvnow();
3118
3119 while (ast_remaining_ms(start, 5000) > 0) {
3120 void *object;
3121
3122 object = ast_sorcery_retrieve_by_id(sorcery, "test", "test");
3123 if (object != previous_object) {
3124 *new_object = object;
3125 return 0;
3126 }
3127 ao2_cleanup(object);
3128 }
3129
3130 return -1;
3131}
3132
3133AST_TEST_DEFINE(stale)
3134{
3135 int res = AST_TEST_FAIL;
3136 struct ast_sorcery *sorcery = NULL;
3137 struct test_data *backend_object;
3138 struct backend_data iterations[] = {
3139 { .salt = 1, .pepper = 2, .exists = 1 },
3140 { .salt = 568729, .pepper = -234123, .exists = 1 },
3141 { .salt = 0, .pepper = 0, .exists = 0 },
3142 };
3143 struct backend_data initial = {
3144 .salt = 0,
3145 .pepper = 0,
3146 .exists = 1,
3147 };
3148 int i;
3149
3150 switch (cmd) {
3151 case TEST_INIT:
3152 info->name = "stale";
3153 info->category = "/res/res_sorcery_memory_cache/";
3154 info->summary = "Ensure that stale objects are replaced with updated objects";
3155 info->description = "This test performs the following:\n"
3156 "\t* Create a sorcery instance with two wizards"
3157 "\t\t* The first is a memory cache that marks items stale after 3 seconds\n"
3158 "\t\t* The second is a mock of a back-end\n"
3159 "\t* Pre-populates the cache by retrieving some initial data from the backend.\n"
3160 "\t* Performs iterations of the following:\n"
3161 "\t\t* Update backend data with new values\n"
3162 "\t\t* Retrieve item from the cache\n"
3163 "\t\t* Ensure the retrieved item does not have the new backend values\n"
3164 "\t\t* Wait for cached object to become stale\n"
3165 "\t\t* Retrieve the stale cached object\n"
3166 "\t\t* Ensure that the stale object retrieved is the same as the fresh one from earlier\n"
3167 "\t\t* Wait for the cache to update with new data\n"
3168 "\t\t* Ensure that new data in the cache matches backend data";
3169 return AST_TEST_NOT_RUN;
3170 case TEST_EXECUTE:
3171 break;
3172 }
3173
3175
3177 if (!sorcery) {
3178 ast_test_status_update(test, "Failed to create sorcery instance\n");
3179 goto cleanup;
3180 }
3181
3182 ast_sorcery_apply_wizard_mapping(sorcery, "test", "memory_cache",
3183 "object_lifetime_stale=3", 1);
3184 ast_sorcery_apply_wizard_mapping(sorcery, "test", "mock", NULL, 0);
3186
3187 /* Prepopulate the cache */
3188 real_backend_data = &initial;
3189
3190 backend_object = ast_sorcery_retrieve_by_id(sorcery, "test", "test");
3191 if (!backend_object) {
3192 ast_test_status_update(test, "Unable to retrieve backend data and populate the cache\n");
3193 goto cleanup;
3194 }
3195 ao2_ref(backend_object, -1);
3196
3197 for (i = 0; i < ARRAY_LEN(iterations); ++i) {
3198 RAII_VAR(struct test_data *, cache_fresh, NULL, ao2_cleanup);
3199 RAII_VAR(struct test_data *, cache_stale, NULL, ao2_cleanup);
3200 RAII_VAR(struct test_data *, cache_new, NULL, ao2_cleanup);
3201
3202 real_backend_data = &iterations[i];
3203
3204 ast_test_status_update(test, "Begininning iteration %d\n", i);
3205
3206 cache_fresh = ast_sorcery_retrieve_by_id(sorcery, "test", "test");
3207 if (!cache_fresh) {
3208 ast_test_status_update(test, "Unable to retrieve fresh cached object\n");
3209 goto cleanup;
3210 }
3211
3212 if (cache_fresh->salt == iterations[i].salt || cache_fresh->pepper == iterations[i].pepper) {
3213 ast_test_status_update(test, "Fresh cached object has unexpected values. Did we hit the backend?\n");
3214 goto cleanup;
3215 }
3216
3217 sleep(5);
3218
3219 cache_stale = ast_sorcery_retrieve_by_id(sorcery, "test", "test");
3220 if (!cache_stale) {
3221 ast_test_status_update(test, "Unable to retrieve stale cached object\n");
3222 goto cleanup;
3223 }
3224
3225 if (cache_stale != cache_fresh) {
3226 ast_test_status_update(test, "Stale cache hit retrieved different object than fresh cache hit\n");
3227 goto cleanup;
3228 }
3229
3230 if (wait_for_cache_update(sorcery, cache_stale, &cache_new)) {
3231 ast_test_status_update(test, "Cache was not updated\n");
3232 goto cleanup;
3233 }
3234
3235 if (iterations[i].exists) {
3236 if (!cache_new) {
3237 ast_test_status_update(test, "Failed to retrieve item from cache when there should be one present\n");
3238 goto cleanup;
3239 } else if (cache_new->salt != iterations[i].salt ||
3240 cache_new->pepper != iterations[i].pepper) {
3241 ast_test_status_update(test, "New cached item has unexpected values\n");
3242 goto cleanup;
3243 }
3244 } else if (cache_new) {
3245 ast_test_status_update(test, "Retrieved a cached item when there should not have been one present\n");
3246 goto cleanup;
3247 }
3248 }
3249
3250 res = AST_TEST_PASS;
3251
3252cleanup:
3253 if (sorcery) {
3255 }
3257 return res;
3258}
3259
3260AST_TEST_DEFINE(full_backend_cache_expiration)
3261{
3262 int res = AST_TEST_FAIL;
3263 struct ast_sorcery *sorcery = NULL;
3264 struct backend_data initial = {
3265 .salt = 0,
3266 .pepper = 0,
3267 .exists = 4,
3268 };
3269 struct ao2_container *objects;
3272 struct timeval start;
3273 struct timespec end;
3274
3275 switch (cmd) {
3276 case TEST_INIT:
3277 info->name = "full_backend_cache_expiration";
3278 info->category = "/res/res_sorcery_memory_cache/";
3279 info->summary = "Ensure that the full backend cache actually caches the backend";
3280 info->description = "This test performs the following:\n"
3281 "\t* Create a sorcery instance with two wizards"
3282 "\t\t* The first is a memory cache that expires objects after 3 seconds and does full backend caching\n"
3283 "\t\t* The second is a mock of a back-end\n"
3284 "\t* Populates the cache by requesting all objects which returns 4.\n"
3285 "\t* Updates the backend to contain a different number of objects, 8.\n"
3286 "\t* Requests all objects and confirms the number returned is only 4.\n"
3287 "\t* Wait for cached objects to expire.\n"
3288 "\t* Requests all objects and confirms the number returned is 8.";
3289 return AST_TEST_NOT_RUN;
3290 case TEST_EXECUTE:
3291 break;
3292 }
3293
3295
3297 if (!sorcery) {
3298 ast_test_status_update(test, "Failed to create sorcery instance\n");
3299 goto cleanup;
3300 }
3301
3302 ast_sorcery_apply_wizard_mapping(sorcery, "test", "memory_cache",
3303 "object_lifetime_maximum=3,full_backend_cache=yes", 1);
3304 ast_sorcery_apply_wizard_mapping(sorcery, "test", "mock", NULL, 0);
3306 ast_sorcery_object_field_register_nodoc(sorcery, "test", "salt", "0", OPT_UINT_T, 0, FLDSET(struct test_data, salt));
3307 ast_sorcery_object_field_register_nodoc(sorcery, "test", "pepper", "0", OPT_UINT_T, 0, FLDSET(struct test_data, pepper));
3308
3309 /* Prepopulate the cache */
3310 real_backend_data = &initial;
3311
3312 /* Get all current objects in the backend */
3314 if (!objects) {
3315 ast_test_status_update(test, "Unable to retrieve all objects in backend and populate cache\n");
3316 goto cleanup;
3317 }
3318 ao2_ref(objects, -1);
3319
3320 /* Update the backend to have a different number of objects */
3321 initial.exists = 8;
3322
3323 /* Get all current objects in the backend */
3325 if (!objects) {
3326 ast_test_status_update(test, "Unable to retrieve all objects in backend and populate cache\n");
3327 goto cleanup;
3328 }
3329
3330 if (ao2_container_count(objects) == initial.exists) {
3331 ast_test_status_update(test, "Number of objects returned is of the current backend and not the cache\n");
3332 ao2_ref(objects, -1);
3333 goto cleanup;
3334 }
3335
3336 ao2_ref(objects, -1);
3337
3340
3341 start = ast_tvnow();
3342 end.tv_sec = start.tv_sec + 5;
3343 end.tv_nsec = start.tv_usec * 1000;
3344
3346 while (ast_cond_timedwait(&cond, &lock, &end) != ETIMEDOUT) {
3347 }
3349
3352
3353 /* Get all current objects in the backend */
3355 if (!objects) {
3356 ast_test_status_update(test, "Unable to retrieve all objects in backend and populate cache\n");
3357 goto cleanup;
3358 }
3359
3360 if (ao2_container_count(objects) != initial.exists) {
3361 ast_test_status_update(test, "Number of objects returned is NOT of the current backend when it should be\n");
3362 ao2_ref(objects, -1);
3363 goto cleanup;
3364 }
3365
3366 ao2_ref(objects, -1);
3367
3368 res = AST_TEST_PASS;
3369
3370cleanup:
3371 if (sorcery) {
3373 }
3375 return res;
3376}
3377
3378AST_TEST_DEFINE(full_backend_cache_stale)
3379{
3380 int res = AST_TEST_FAIL;
3381 struct ast_sorcery *sorcery = NULL;
3382 struct backend_data initial = {
3383 .salt = 0,
3384 .pepper = 0,
3385 .exists = 4,
3386 };
3387 struct ao2_container *objects;
3390 struct timeval start;
3391 struct timespec end;
3392
3393 switch (cmd) {
3394 case TEST_INIT:
3395 info->name = "full_backend_cache_stale";
3396 info->category = "/res/res_sorcery_memory_cache/";
3397 info->summary = "Ensure that the full backend cache works with staleness";
3398 info->description = "This test performs the following:\n"
3399 "\t* Create a sorcery instance with two wizards"
3400 "\t\t* The first is a memory cache that stales objects after 1 second and does full backend caching\n"
3401 "\t\t* The second is a mock of a back-end\n"
3402 "\t* Populates the cache by requesting all objects which returns 4.\n"
3403 "\t* Wait for objects to go stale.\n"
3404 "\t* Updates the backend to contain a different number of objects, 8.\""
3405 "\t* Requests all objects and confirms the number returned is only 4.\n"
3406 "\t* Wait for objects to be refreshed from backend.\n"
3407 "\t* Requests all objects and confirms the number returned is 8.";
3408 return AST_TEST_NOT_RUN;
3409 case TEST_EXECUTE:
3410 break;
3411 }
3412
3414
3417
3419 if (!sorcery) {
3420 ast_test_status_update(test, "Failed to create sorcery instance\n");
3421 goto cleanup;
3422 }
3423
3424 ast_sorcery_apply_wizard_mapping(sorcery, "test", "memory_cache",
3425 "object_lifetime_stale=1,full_backend_cache=yes", 1);
3426 ast_sorcery_apply_wizard_mapping(sorcery, "test", "mock", NULL, 0);
3428 ast_sorcery_object_field_register_nodoc(sorcery, "test", "salt", "0", OPT_UINT_T, 0, FLDSET(struct test_data, salt));
3429 ast_sorcery_object_field_register_nodoc(sorcery, "test", "pepper", "0", OPT_UINT_T, 0, FLDSET(struct test_data, pepper));
3430
3431 /* Prepopulate the cache */
3432 real_backend_data = &initial;
3433
3434 /* Get all current objects in the backend */
3436 if (!objects) {
3437 ast_test_status_update(test, "Unable to retrieve all objects in backend and populate cache\n");
3438 goto cleanup;
3439 }
3440 ao2_ref(objects, -1);
3441
3442 start = ast_tvnow();
3443 end.tv_sec = start.tv_sec + 5;
3444 end.tv_nsec = start.tv_usec * 1000;
3445
3447 while (ast_cond_timedwait(&cond, &lock, &end) != ETIMEDOUT) {
3448 }
3450
3451 initial.exists = 8;
3452
3453 /* Get all current objects in the backend */
3455 if (!objects) {
3456 ast_test_status_update(test, "Unable to retrieve all objects in backend and populate cache\n");
3457 goto cleanup;
3458 }
3459
3460 if (ao2_container_count(objects) == initial.exists) {
3461 ast_test_status_update(test, "Number of objects returned is of the backend and not the cache\n");
3462 ao2_ref(objects, -1);
3463 goto cleanup;
3464 }
3465
3466 ao2_ref(objects, -1);
3467
3468 start = ast_tvnow();
3469 end.tv_sec = start.tv_sec + 5;
3470 end.tv_nsec = start.tv_usec * 1000;
3471
3473 while (ast_cond_timedwait(&cond, &lock, &end) != ETIMEDOUT) {
3474 }
3476
3477 /* Get all current objects in the backend */
3479 if (!objects) {
3480 ast_test_status_update(test, "Unable to retrieve all objects in backend and populate cache\n");
3481 goto cleanup;
3482 }
3483
3484 if (ao2_container_count(objects) != initial.exists) {
3485 ast_test_status_update(test, "Number of objects returned is not of backend\n");
3486 ao2_ref(objects, -1);
3487 goto cleanup;
3488 }
3489
3490 ao2_ref(objects, -1);
3491
3492 start = ast_tvnow();
3493 end.tv_sec = start.tv_sec + 5;
3494 end.tv_nsec = start.tv_usec * 1000;
3495
3497 while (ast_cond_timedwait(&cond, &lock, &end) != ETIMEDOUT) {
3498 }
3500
3501 res = AST_TEST_PASS;
3502
3503cleanup:
3504 if (sorcery) {
3506 }
3510 return res;
3511}
3512
3513#endif
3514
3515static int unload_module(void)
3516{
3517 AST_TEST_UNREGISTER(open_with_valid_options);
3518 AST_TEST_UNREGISTER(open_with_invalid_options);
3519 AST_TEST_UNREGISTER(create_and_retrieve);
3521 AST_TEST_UNREGISTER(delete);
3522 AST_TEST_UNREGISTER(maximum_objects);
3523 AST_TEST_UNREGISTER(expiration);
3524 AST_TEST_UNREGISTER(stale);
3525 AST_TEST_UNREGISTER(full_backend_cache_expiration);
3526 AST_TEST_UNREGISTER(full_backend_cache_stale);
3527
3528 ast_manager_unregister("SorceryMemoryCacheExpireObject");
3529 ast_manager_unregister("SorceryMemoryCacheExpire");
3530 ast_manager_unregister("SorceryMemoryCacheStaleObject");
3531 ast_manager_unregister("SorceryMemoryCacheStale");
3532 ast_manager_unregister("SorceryMemoryCachePopulate");
3533
3535
3537
3538 /*
3539 * XXX There is the potential to leak memory if there are pending
3540 * next-cache-expiration and stale-cache-update tasks in the scheduler.
3541 */
3542 if (sched) {
3544 sched = NULL;
3545 }
3546
3548 caches = NULL;
3549
3550 return 0;
3551}
3552
3553static int load_module(void)
3554{
3555 int res;
3556
3560 if (!caches) {
3561 ast_log(LOG_ERROR, "Failed to create container for configured caches\n");
3562 unload_module();
3564 }
3565
3567 if (!sched) {
3568 ast_log(LOG_ERROR, "Failed to create scheduler for cache management\n");
3569 unload_module();
3571 }
3572
3574 ast_log(LOG_ERROR, "Failed to create scheduler thread for cache management\n");
3575 unload_module();
3577 }
3578
3580 unload_module();
3582 }
3583
3590
3591 if (res) {
3592 unload_module();
3594 }
3595
3596 /* This causes the stale unit test to execute last, so if a sorcery instance persists
3597 * longer than expected subsequent unit tests don't fail when setting it up.
3598 */
3599 AST_TEST_REGISTER(stale);
3600 AST_TEST_REGISTER(open_with_valid_options);
3601 AST_TEST_REGISTER(open_with_invalid_options);
3602 AST_TEST_REGISTER(create_and_retrieve);
3604 AST_TEST_REGISTER(delete);
3605 AST_TEST_REGISTER(maximum_objects);
3606 AST_TEST_REGISTER(expiration);
3607 AST_TEST_REGISTER(full_backend_cache_expiration);
3608 AST_TEST_REGISTER(full_backend_cache_stale);
3609
3611}
3612
3614 .support_level = AST_MODULE_SUPPORT_CORE,
3615 .load = load_module,
3616 .unload = unload_module,
3617 .load_pri = AST_MODPRI_REALTIME_DRIVER,
ast_cond_t cond
Definition: app_sla.c:336
ast_mutex_t lock
Definition: app_sla.c:337
char * strsep(char **str, const char *delims)
Asterisk main include file. File version handling, generic pbx functions.
#define ast_free(a)
Definition: astmm.h:180
#define ast_strdup(str)
A wrapper for strdup()
Definition: astmm.h:241
#define ast_strdupa(s)
duplicate a string in memory from the stack
Definition: astmm.h:298
#define ast_asprintf(ret, fmt,...)
A wrapper for asprintf()
Definition: astmm.h:267
#define ast_log
Definition: astobj2.c:42
#define ao2_iterator_next(iter)
Definition: astobj2.h:1911
#define ao2_link(container, obj)
Add an object to a container.
Definition: astobj2.h:1532
@ CMP_MATCH
Definition: astobj2.h:1027
@ CMP_STOP
Definition: astobj2.h:1028
#define ao2_rdlock(a)
Definition: astobj2.h:718
@ AO2_ALLOC_OPT_LOCK_NOLOCK
Definition: astobj2.h:367
@ AO2_ALLOC_OPT_LOCK_RWLOCK
Definition: astobj2.h:365
@ AO2_ALLOC_OPT_LOCK_MUTEX
Definition: astobj2.h:363
#define ao2_wrlock(a)
Definition: astobj2.h:719
#define ao2_callback(c, flags, cb_fn, arg)
ao2_callback() is a generic function that applies cb_fn() to all objects in a container,...
Definition: astobj2.h:1693
int ao2_container_count(struct ao2_container *c)
Returns the number of elements in a container.
#define ao2_cleanup(obj)
Definition: astobj2.h:1934
#define ao2_callback_data(container, flags, cb_fn, arg, data)
Definition: astobj2.h:1723
#define ao2_unlink(container, obj)
Remove an object from a container.
Definition: astobj2.h:1578
#define ao2_link_flags(container, obj, flags)
Add an object to a container.
Definition: astobj2.h:1554
#define ao2_trywrlock(a)
Definition: astobj2.h:741
#define ao2_find(container, arg, flags)
Definition: astobj2.h:1736
struct ao2_iterator ao2_iterator_init(struct ao2_container *c, int flags) attribute_warn_unused_result
Create an iterator for a container.
#define ao2_unlock(a)
Definition: astobj2.h:729
#define ao2_lock(a)
Definition: astobj2.h:717
#define ao2_ref(o, delta)
Reference/unreference an object and return the old refcount.
Definition: astobj2.h:459
#define ao2_alloc_options(data_size, destructor_fn, options)
Definition: astobj2.h:404
#define ao2_bump(obj)
Bump refcount on an AO2 object by one, returning the object.
Definition: astobj2.h:480
void ao2_iterator_destroy(struct ao2_iterator *iter)
Destroy a container iterator.
@ OBJ_SEARCH_PARTIAL_KEY
The arg parameter is a partial search key similar to OBJ_SEARCH_KEY.
Definition: astobj2.h:1116
@ OBJ_SEARCH_OBJECT
The arg parameter is an object of the same type.
Definition: astobj2.h:1087
@ OBJ_NOLOCK
Assume that the ao2_container is already locked.
Definition: astobj2.h:1063
@ OBJ_NODATA
Definition: astobj2.h:1044
@ OBJ_SEARCH_MASK
Search option field mask.
Definition: astobj2.h:1072
@ OBJ_MULTIPLE
Definition: astobj2.h:1049
@ OBJ_UNLINK
Definition: astobj2.h:1039
@ OBJ_SEARCH_KEY
The arg parameter is a search key, but is not an object.
Definition: astobj2.h:1101
#define ao2_alloc(data_size, destructor_fn)
Definition: astobj2.h:409
#define ao2_container_alloc_hash(ao2_options, container_options, n_buckets, hash_fn, sort_fn, cmp_fn)
Allocate and initialize a hash container with the desired number of buckets.
Definition: astobj2.h:1303
static PGresult * result
Definition: cel_pgsql.c:84
static const char type[]
Definition: chan_ooh323.c:109
Standard Command Line Interface.
#define CLI_SHOWUSAGE
Definition: cli.h:45
#define CLI_SUCCESS
Definition: cli.h:44
int ast_cli_unregister_multiple(struct ast_cli_entry *e, int len)
Unregister multiple commands.
Definition: clicompat.c:30
#define AST_CLI_DEFINE(fn, txt,...)
Definition: cli.h:197
void ast_cli(int fd, const char *fmt,...)
Definition: clicompat.c:6
char * ast_cli_complete(const char *word, const char *const choices[], int pos)
Definition: main/cli.c:1853
#define AST_CLI_ONOFF(x)
return On or Off depending on the argument. This is used in many places in CLI command,...
Definition: cli.h:78
@ CLI_INIT
Definition: cli.h:152
@ CLI_GENERATE
Definition: cli.h:153
#define CLI_FAILURE
Definition: cli.h:46
#define ast_cli_register_multiple(e, len)
Register multiple commands.
Definition: cli.h:265
static void update(int code_size, int y, int wi, int fi, int dq, int sr, int dqsez, struct g726_state *state_ptr)
Definition: codec_g726.c:367
short word
#define FLDSET(type,...)
Convert a struct and list of fields to an argument list of field offsets.
@ OPT_UINT_T
Type for default option handler for unsigned integers.
char * end
Definition: eagi_proxy.c:73
static const char name[]
Definition: format_mp3.c:68
static int exists(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
Definition: func_logic.c:185
static int regex(struct ast_channel *chan, const char *cmd, char *parse, char *buf, size_t len)
static int uuid(struct ast_channel *chan, const char *cmd, char *data, char *buf, size_t len)
Definition: func_uuid.c:52
void astman_send_error(struct mansession *s, const struct message *m, char *error)
Send error in manager transaction.
Definition: manager.c:1986
void astman_send_ack(struct mansession *s, const struct message *m, char *msg)
Send ack in manager transaction.
Definition: manager.c:2018
const char * astman_get_header(const struct message *m, char *var)
Get header from manager transaction.
Definition: manager.c:1647
int ast_manager_unregister(const char *action)
Unregister a registered manager command.
Definition: manager.c:7697
Max Heap data structure.
struct ast_heap * ast_heap_destroy(struct ast_heap *h)
Destroy a max heap.
Definition: heap.c:146
#define ast_heap_create(init_height, cmp_fn, index_offset)
Create a max heap.
Definition: heap.h:100
void * ast_heap_pop(struct ast_heap *h)
Pop the max element off of the heap.
Definition: heap.c:262
void * ast_heap_remove(struct ast_heap *h, void *elm)
Remove a specific element from a heap.
Definition: heap.c:251
#define ast_heap_push(h, elm)
Push an element on to a heap.
Definition: heap.h:125
void * ast_heap_peek(struct ast_heap *h, unsigned int index)
Peek at an element on a heap.
Definition: heap.c:267
static char prefix[MAX_PREFIX]
Definition: http.c:144
int ast_variable_lists_match(const struct ast_variable *left, const struct ast_variable *right, int exact_match)
Tests 2 variable lists to see if they match.
Definition: main/config.c:955
void ast_variables_destroy(struct ast_variable *var)
Free variable list.
Definition: extconf.c:1262
#define ast_debug(level,...)
Log a DEBUG message.
#define LOG_ERROR
#define LOG_WARNING
#define ast_cond_destroy(cond)
Definition: lock.h:206
#define ast_cond_init(cond, attr)
Definition: lock.h:205
#define ast_cond_timedwait(cond, mutex, time)
Definition: lock.h:210
#define ast_mutex_init(pmutex)
Definition: lock.h:190
#define ast_mutex_unlock(a)
Definition: lock.h:194
pthread_cond_t ast_cond_t
Definition: lock.h:182
#define ast_mutex_destroy(a)
Definition: lock.h:192
#define ast_mutex_lock(a)
Definition: lock.h:193
#define ast_cond_signal(cond)
Definition: lock.h:207
The AMI - Asterisk Manager Interface - is a TCP protocol created to manage Asterisk with third-party ...
#define EVENT_FLAG_SYSTEM
Definition: manager.h:75
#define ast_manager_register_xml(action, authority, func)
Register a manager callback using XML documentation to describe the manager.
Definition: manager.h:192
Asterisk module definitions.
@ AST_MODFLAG_LOAD_ORDER
Definition: module.h:331
@ AST_MODFLAG_GLOBAL_SYMBOLS
Definition: module.h:330
#define AST_MODULE_INFO(keystr, flags_to_set, desc, fields...)
Definition: module.h:557
@ AST_MODPRI_REALTIME_DRIVER
Definition: module.h:337
@ AST_MODULE_SUPPORT_CORE
Definition: module.h:121
#define ASTERISK_GPL_KEY
The text the key() function should return.
Definition: module.h:46
@ AST_MODULE_LOAD_SUCCESS
Definition: module.h:70
@ AST_MODULE_LOAD_DECLINE
Module has failed to load, may be in an inconsistent state.
Definition: module.h:78
def info(msg)
static void * cleanup(void *unused)
Definition: pbx_realtime.c:124
struct ao2_container * cache
Definition: pbx_realtime.c:77
static int reload(void)
static struct ast_sorcery * sorcery
static struct stale_update_task_data * stale_update_task_data_alloc(struct ast_sorcery *sorcery, struct sorcery_memory_cache *cache, const char *type, void *object)
static char * sorcery_memory_cache_dump(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
static int object_stale_callback(void *obj, void *arg, int flags)
static int stale_item_update(const void *data)
static int sorcery_memory_cache_ami_stale_object(struct mansession *s, const struct message *m)
static void * sorcery_memory_cache_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id)
static char * sorcery_memory_cache_stale(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
static void stale_cache_update_task_data_destructor(void *obj)
static char * sorcery_memory_cache_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
#define PASSTHRU_UPDATE_THREAD_ID
static struct ast_threadstorage passthru_update_id_storage
static int configuration_parse_unsigned_integer(const char *value, unsigned int *result)
static int sorcery_memory_cache_print_object(void *obj, void *arg, int flags)
#define CACHES_CONTAINER_BUCKET_SIZE
The bucket size for the container of caches.
static struct ast_sched_context * sched
Scheduler for cache management.
static int sorcery_memory_cached_object_cmp(void *obj, void *arg, int flags)
static void sorcery_memory_cache_destructor(void *obj)
static void end_passthru_update(void)
static int sorcery_memory_cache_ami_stale(struct mansession *s, const struct message *m)
static void set_passthru_update(uint32_t value)
#define CACHE_HEAP_INIT_HEIGHT
Height of heap for cache object heap. Allows 31 initial objects.
static void sorcery_memory_cache_retrieve_regex(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, const char *regex)
static void * sorcery_memory_cache_retrieve_fields(const struct ast_sorcery *sorcery, void *data, const char *type, const struct ast_variable *fields)
static char * sorcery_memory_cache_complete_object_name(const char *cache_name, const char *word, int state)
static void memory_cache_stale_check_object(const struct ast_sorcery *sorcery, struct sorcery_memory_cache *cache, struct sorcery_memory_cached_object *cached)
static int sorcery_memory_cache_fields_cmp(void *obj, void *arg, int flags)
static void stale_update_task_data_destructor(void *obj)
static int remove_from_cache(struct sorcery_memory_cache *cache, const char *id, int reschedule)
static struct ast_cli_entry cli_memory_cache[]
static void sorcery_memory_cache_reload(void *data, const struct ast_sorcery *sorcery, const char *type)
static int sorcery_memory_cache_cmp(void *obj, void *arg, int flags)
static void sorcery_memory_cache_load(void *data, const struct ast_sorcery *sorcery, const char *type)
static int sorcery_memory_cache_hash(const void *obj, int flags)
static void * sorcery_memory_cache_open(const char *data)
static int sorcery_memory_cache_create(const struct ast_sorcery *sorcery, void *data, void *object)
static int sorcery_memory_cache_ami_expire(struct mansession *s, const struct message *m)
static struct ast_sorcery_wizard memory_cache_object_wizard
static struct stale_cache_update_task_data * stale_cache_update_task_data_alloc(struct ast_sorcery *sorcery, struct sorcery_memory_cache *cache, const char *type)
static void start_passthru_update(void)
static int expire_objects_from_cache(const void *data)
static struct sorcery_memory_cached_object * sorcery_memory_cached_object_alloc(const struct ast_sorcery *sorcery, const struct sorcery_memory_cache *cache, void *object)
static int sorcery_memory_cache_delete(const struct ast_sorcery *sorcery, void *data, void *object)
static void memory_cache_full_update(const struct ast_sorcery *sorcery, const char *type, struct sorcery_memory_cache *cache)
static int is_passthru_update(void)
static int age_cmp(void *a, void *b)
#define CACHE_CONTAINER_BUCKET_SIZE
The default bucket size for the container of objects in the cache.
static void memory_cache_populate(const struct ast_sorcery *sorcery, const char *type, struct sorcery_memory_cache *cache)
static int sorcery_memory_cached_object_hash(const void *obj, int flags)
static void sorcery_memory_cache_close(void *data)
static int sorcery_memory_cache_ami_populate(struct mansession *s, const struct message *m)
static char * sorcery_memory_cache_expire(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
static int object_add_to_cache_callback(void *obj, void *arg, void *data, int flags)
static void memory_cache_stale_update_object(const struct ast_sorcery *sorcery, struct sorcery_memory_cache *cache, struct sorcery_memory_cached_object *cached)
static void remove_all_from_cache(struct sorcery_memory_cache *cache)
static int stale_cache_update(const void *data)
static void mark_all_as_stale_in_cache(struct sorcery_memory_cache *cache)
static void memory_cache_stale_check(const struct ast_sorcery *sorcery, struct sorcery_memory_cache *cache)
static int remove_oldest_from_cache(struct sorcery_memory_cache *cache)
static int load_module(void)
static void memory_cache_stale_update_full(const struct ast_sorcery *sorcery, struct sorcery_memory_cache *cache, const char *type)
#define FORMAT
static int schedule_cache_expiration(struct sorcery_memory_cache *cache)
static void sorcery_memory_cache_retrieve_multiple(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, const struct ast_variable *fields)
static int unload_module(void)
static int add_to_cache(struct sorcery_memory_cache *cache, struct sorcery_memory_cached_object *cached_object)
static char * sorcery_memory_cache_complete_name(const char *word, int state)
static void sorcery_memory_cache_retrieve_prefix(const struct ast_sorcery *sorcery, void *data, const char *type, struct ao2_container *objects, const char *prefix, const size_t prefix_len)
static char * sorcery_memory_cache_populate(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
static struct ao2_container * caches
Container of created caches.
static int sorcery_memory_cache_ami_expire_object(struct mansession *s, const struct message *m)
static int mark_object_as_stale_in_cache(struct sorcery_memory_cache *cache, const char *id)
static void sorcery_memory_cached_object_destructor(void *obj)
#define NULL
Definition: resample.c:96
Scheduler Routines (derived from cheops)
#define AST_SCHED_DEL_UNREF(sched, id, refcall)
schedule task to get deleted and call unref function
Definition: sched.h:82
int ast_sched_add(struct ast_sched_context *con, int when, ast_sched_cb callback, const void *data) attribute_warn_unused_result
Adds a scheduled event.
Definition: sched.c:567
void ast_sched_context_destroy(struct ast_sched_context *c)
destroys a schedule context
Definition: sched.c:271
int ast_sched_start_thread(struct ast_sched_context *con)
Start a thread for processing scheduler entries.
Definition: sched.c:197
struct ast_sched_context * ast_sched_context_create(void)
Create a scheduler context.
Definition: sched.c:238
Sorcery Data Access Layer API.
#define ast_sorcery_apply_wizard_mapping(sorcery, type, name, data, caching)
Apply additional object wizard mappings.
Definition: sorcery.h:510
#define ast_sorcery_unref(sorcery)
Decrease the reference count of a sorcery structure.
Definition: sorcery.h:1500
const char * ast_sorcery_object_get_id(const void *object)
Get the unique identifier of a sorcery object.
Definition: sorcery.c:2317
#define ast_sorcery_object_field_register_nodoc(sorcery, type, name, default_val, opt_type, flags,...)
Register a field within an object without documentation.
Definition: sorcery.h:987
int ast_sorcery_wizard_unregister(const struct ast_sorcery_wizard *interface)
Unregister a sorcery wizard.
Definition: sorcery.c:474
#define ast_sorcery_objectset_create(sorcery, object)
Create an object set (KVP list) for an object.
Definition: sorcery.h:1137
@ AST_RETRIEVE_FLAG_MULTIPLE
Return all matching objects.
Definition: sorcery.h:120
@ AST_RETRIEVE_FLAG_ALL
Perform no matching, return all objects.
Definition: sorcery.h:123
const char * ast_sorcery_get_module(const struct ast_sorcery *sorcery)
Get the module that has opened the provided sorcery instance.
Definition: sorcery.c:2536
const char * ast_sorcery_object_get_type(const void *object)
Get the type of a sorcery object.
Definition: sorcery.c:2329
void * ast_sorcery_retrieve_by_id(const struct ast_sorcery *sorcery, const char *type, const char *id)
Retrieve an object using its unique identifier.
Definition: sorcery.c:1853
#define ast_sorcery_internal_object_register(sorcery, type, alloc, transform, apply)
Register an internal, hidden object type.
Definition: sorcery.h:867
void * ast_sorcery_generic_alloc(size_t size, ao2_destructor_fn destructor)
Allocate a generic sorcery capable object.
Definition: sorcery.c:1728
#define ast_sorcery_wizard_register(interface)
See __ast_sorcery_wizard_register()
Definition: sorcery.h:383
void * ast_sorcery_alloc(const struct ast_sorcery *sorcery, const char *type, const char *id)
Allocate an object.
Definition: sorcery.c:1744
#define ast_sorcery_apply_default(sorcery, type, name, data)
Definition: sorcery.h:476
#define ast_sorcery_open()
Open a new sorcery structure.
Definition: sorcery.h:406
void * ast_sorcery_retrieve_by_fields(const struct ast_sorcery *sorcery, const char *type, unsigned int flags, struct ast_variable *fields)
Retrieve an object or multiple objects using specific fields.
Definition: sorcery.c:1897
@ AST_SORCERY_APPLY_SUCCESS
Definition: sorcery.h:427
static force_inline int attribute_pure ast_str_hash(const char *str)
Compute a hash value on a string.
Definition: strings.h:1259
int attribute_pure ast_true(const char *val)
Make sure something is true. Determine if a string containing a boolean value is "true"....
Definition: utils.c:2199
static force_inline int attribute_pure ast_strlen_zero(const char *s)
Definition: strings.h:65
Generic container type.
When we need to walk through a container, we use an ao2_iterator to keep track of the current positio...
Definition: astobj2.h:1821
const int fd
Definition: cli.h:159
descriptor for a cli entry.
Definition: cli.h:171
char * command
Definition: cli.h:186
const char * usage
Definition: cli.h:177
Definition: heap.c:36
Structure for mutex and tracking information.
Definition: lock.h:139
Interface for a sorcery wizard.
Definition: sorcery.h:276
const char * name
Name of the wizard.
Definition: sorcery.h:278
Full structure for sorcery.
Definition: sorcery.c:230
Structure for variables, used for configurations and for channel variables.
In case you didn't read that giant block of text above the mansession_session struct,...
Definition: manager.c:327
Structure used to pass data for printing cached object information.
struct sorcery_memory_cache * cache
The sorcery memory cache.
struct ast_cli_args * a
The CLI arguments.
Definition: sched.c:76
Structure used for fields comparison.
const char * prefix
Prefix for matching object id.
struct ao2_container * container
Optional container to put object into.
struct sorcery_memory_cache * cache
The sorcery memory cache.
const size_t prefix_len
Prefix length in bytes for matching object id.
const struct ast_sorcery * sorcery
Pointer to the sorcery structure.
const struct ast_variable * fields
Pointer to the fields to check.
regex_t * regex
Regular expression for checking object id.
Structure for storing a memory cache.
int stale_update_sched_id
scheduler id of stale update task
char * name
The name of the memory cache.
unsigned int maximum_objects
The maximum number of objects permitted in the cache, 0 if no limit.
unsigned int expire_on_reload
Whether all objects are expired when the object type is reloaded, 0 if disabled.
unsigned int object_lifetime_maximum
The maximum time (in seconds) an object will stay in the cache, 0 if no limit.
unsigned int object_lifetime_stale
The amount of time (in seconds) before an object is marked as stale, 0 if disabled.
struct ao2_container * objects
Objects in the cache.
char * object_type
The type of object we are caching.
struct ast_heap * object_heap
Heap of cached objects. Oldest object is at the top.
unsigned int full_backend_cache
Whether this is a cache of the entire backend, 0 if disabled.
int expire_id
Scheduler item for expiring oldest object.
const struct ast_sorcery * sorcery
An unreffed pointer to the sorcery instance, accessible only with lock held.
Structure for stored a cached object.
struct ast_variable * objectset
Cached objectset for field and regex retrieval.
ssize_t __heap_index
index required by heap
struct timeval created
The time at which the object was created.
int stale_update_sched_id
scheduler id of stale update task
struct sorcery_memory_cache * cache
struct sorcery_memory_cache * cache
userdata associated with baseline taskprocessor test
Sorcery object created based on backend data.
SORCERY_OBJECT(details)
Dummy sorcery object.
Definition: test_sorcery.c:44
int value
Definition: syslog.c:37
Test Framework API.
@ TEST_INIT
Definition: test.h:200
@ TEST_EXECUTE
Definition: test.h:201
#define AST_TEST_REGISTER(cb)
Definition: test.h:127
#define ast_test_status_update(a, b, c...)
Definition: test.h:129
#define AST_TEST_UNREGISTER(cb)
Definition: test.h:128
#define ast_test_suite_event_notify(s, f,...)
Definition: test.h:189
#define AST_TEST_DEFINE(hdr)
Definition: test.h:126
@ AST_TEST_PASS
Definition: test.h:195
@ AST_TEST_FAIL
Definition: test.h:196
@ AST_TEST_NOT_RUN
Definition: test.h:194
static struct test_options options
static struct test_val b
static struct test_val a
static struct ast_sorcery * alloc_and_initialize_sorcery(void)
Definition: test_sorcery.c:332
static void * test_sorcery_object_alloc(const char *id)
Internal function to allocate a test object.
Definition: test_sorcery.c:61
static void * mock_retrieve_id(const struct ast_sorcery *sorcery, void *data, const char *type, const char *id)
Callback for retrieving sorcery object by ID.
static struct ast_sorcery_wizard mock_wizard
A mock sorcery wizard used for the stale test.
static void * test_data_alloc(const char *id)
Allocation callback for test_data sorcery object.
void * ast_threadstorage_get(struct ast_threadstorage *ts, size_t init_size)
Retrieve thread storage.
#define AST_THREADSTORAGE(name)
Define a thread storage variable.
Definition: threadstorage.h:86
struct timeval ast_samp2tv(unsigned int _nsamp, unsigned int _rate)
Returns a timeval corresponding to the duration of n samples at rate r. Useful to convert samples to ...
Definition: time.h:282
int ast_tvcmp(struct timeval _a, struct timeval _b)
Compress two struct timeval instances returning -1, 0, 1 if the first arg is smaller,...
Definition: time.h:137
int ast_remaining_ms(struct timeval start, int max_ms)
Calculate remaining milliseconds given a starting timestamp and upper bound.
Definition: utils.c:2281
struct timeval ast_tvadd(struct timeval a, struct timeval b)
Returns the sum of two timevals a + b.
Definition: extconf.c:2282
struct timeval ast_tvsub(struct timeval a, struct timeval b)
Returns the difference of two timevals a - b.
Definition: extconf.c:2297
int64_t ast_tvdiff_ms(struct timeval end, struct timeval start)
Computes the difference (in milliseconds) between two struct timeval instances.
Definition: time.h:107
struct timeval ast_tvnow(void)
Returns current timeval. Meant to replace calls to gettimeofday().
Definition: time.h:159
#define RAII_VAR(vartype, varname, initval, dtor)
Declare a variable that will call a destructor function when it goes out of scope.
Definition: utils.h:941
#define ast_assert(a)
Definition: utils.h:739
#define ARRAY_LEN(a)
Definition: utils.h:666
#define MAX(a, b)
Definition: utils.h:233
#define AST_UUID_STR_LEN
Definition: uuid.h:27
char * ast_uuid_generate_str(char *buf, size_t size)
Generate a UUID string.
Definition: uuid.c:141