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