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