2015-05-16 19:02:50 -03:00
/*
* Asterisk -- An open source telephony toolkit.
*
* Copyright (C) 2015, Digium, Inc.
*
* Joshua Colp <jcolp@digium.com>
*
* See http://www.asterisk.org for more information about
* the Asterisk project. Please do not directly contact
* any of the maintainers of this project for assistance;
* the project provides a web site, mailing lists and IRC
* channels for your use.
*
* This program is free software, distributed under the terms of
* the GNU General Public License Version 2. See the LICENSE file
* at the top of the source tree.
*/
/*!
* \file
*
* \brief Sorcery Memory Cache Object Wizard
*
* \author Joshua Colp <jcolp@digium.com>
*/
/*** MODULEINFO
<support_level>core</support_level>
***/
# include "asterisk.h"
# include "asterisk/module.h"
# include "asterisk/sorcery.h"
# include "asterisk/astobj2.h"
# include "asterisk/sched.h"
# include "asterisk/test.h"
2015-05-20 15:19:27 -05:00
# include "asterisk/heap.h"
2015-05-26 09:34:47 -03:00
# include "asterisk/cli.h"
# include "asterisk/manager.h"
/*** DOCUMENTATION
<manager name="SorceryMemoryCacheExpireObject" language="en_US">
<synopsis>
Expire (remove) an object from a sorcery memory cache.
</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
<parameter name="Cache" required="true">
<para>The name of the cache to expire the object from.</para>
</parameter>
<parameter name="Object" required="true">
<para>The name of the object to expire.</para>
</parameter>
</syntax>
<description>
2020-06-10 06:35:50 -03:00
<para>Expires (removes) an object from a sorcery memory cache. If full backend caching is enabled
this action is not available and will fail. In this case the SorceryMemoryCachePopulate or
SorceryMemoryCacheExpire AMI actions must be used instead.</para>
2015-05-26 09:34:47 -03:00
</description>
</manager>
<manager name="SorceryMemoryCacheExpire" language="en_US">
<synopsis>
Expire (remove) ALL objects from a sorcery memory cache.
</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
<parameter name="Cache" required="true">
<para>The name of the cache to expire all objects from.</para>
</parameter>
</syntax>
<description>
<para>Expires (removes) ALL objects from a sorcery memory cache.</para>
</description>
</manager>
<manager name="SorceryMemoryCacheStaleObject" language="en_US">
<synopsis>
Mark an object in a sorcery memory cache as stale.
</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
<parameter name="Cache" required="true">
<para>The name of the cache to mark the object as stale in.</para>
</parameter>
<parameter name="Object" required="true">
<para>The name of the object to mark as stale.</para>
</parameter>
2017-01-25 15:26:53 -06:00
<parameter name="Reload" required="false">
<para>If true, then immediately reload the object from the backend cache instead of waiting for the next retrieval</para>
</parameter>
2015-05-26 09:34:47 -03:00
</syntax>
<description>
<para>Marks an object as stale within a sorcery memory cache.</para>
</description>
</manager>
<manager name="SorceryMemoryCacheStale" language="en_US">
<synopsis>
Marks ALL objects in a sorcery memory cache as stale.
</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
<parameter name="Cache" required="true">
<para>The name of the cache to mark all object as stale in.</para>
</parameter>
</syntax>
<description>
<para>Marks ALL objects in a sorcery memory cache as stale.</para>
</description>
</manager>
2015-12-05 12:01:55 -04:00
<manager name="SorceryMemoryCachePopulate" language="en_US">
<synopsis>
Expire all objects from a memory cache and populate it with all objects from the backend.
</synopsis>
<syntax>
<xi:include xpointer="xpointer(/docs/manager[@name='Login']/syntax/parameter[@name='ActionID'])" />
<parameter name="Cache" required="true">
<para>The name of the cache to populate.</para>
</parameter>
</syntax>
<description>
<para>Expires all objects from a memory cache and populate it with all objects from the backend.</para>
</description>
</manager>
2015-05-26 09:34:47 -03:00
***/
2015-05-16 19:02:50 -03:00
/*! \brief Structure for storing a memory cache */
struct sorcery_memory_cache {
/*! \brief The name of the memory cache */
char * name ;
/*! \brief Objects in the cache */
struct ao2_container * objects ;
/*! \brief The maximum number of objects permitted in the cache, 0 if no limit */
unsigned int maximum_objects ;
/*! \brief The maximum time (in seconds) an object will stay in the cache, 0 if no limit */
unsigned int object_lifetime_maximum ;
/*! \brief The amount of time (in seconds) before an object is marked as stale, 0 if disabled */
unsigned int object_lifetime_stale ;
2015-12-05 12:01:55 -04:00
/*! \brief Whether all objects are expired when the object type is reloaded, 0 if disabled */
2015-05-16 19:02:50 -03:00
unsigned int expire_on_reload ;
2015-12-05 12:01:55 -04:00
/*! \brief Whether this is a cache of the entire backend, 0 if disabled */
unsigned int full_backend_cache ;
2015-05-20 15:19:27 -05:00
/*! \brief Heap of cached objects. Oldest object is at the top. */
struct ast_heap * object_heap ;
2015-05-20 19:35:54 -03:00
/*! \brief Scheduler item for expiring oldest object. */
int expire_id ;
2015-12-05 12:01:55 -04:00
/*! \brief scheduler id of stale update task */
int stale_update_sched_id ;
/*! \brief An unreffed pointer to the sorcery instance, accessible only with lock held */
const struct ast_sorcery * sorcery ;
/*! \brief The type of object we are caching */
char * object_type ;
2015-09-30 17:28:19 -05:00
/*! TRUE if trying to stop the oldest object expiration scheduler item. */
unsigned int del_expire : 1 ;
2015-05-20 19:35:54 -03:00
# ifdef TEST_FRAMEWORK
/*! \brief Variable used to indicate we should notify a test when we reach empty */
unsigned int cache_notify ;
/*! \brief Mutex lock used for signaling when the cache has reached empty */
ast_mutex_t lock ;
/*! \brief Condition used for signaling when the cache has reached empty */
ast_cond_t cond ;
/*! \brief Variable that is set when the cache has reached empty */
unsigned int cache_completed ;
# endif
2015-05-16 19:02:50 -03:00
} ;
/*! \brief Structure for stored a cached object */
struct sorcery_memory_cached_object {
/*! \brief The cached object */
void * object ;
/*! \brief The time at which the object was created */
struct timeval created ;
2015-05-20 15:19:27 -05:00
/*! \brief index required by heap */
ssize_t __heap_index ;
2015-05-26 13:01:24 -05:00
/*! \brief scheduler id of stale update task */
int stale_update_sched_id ;
2015-12-05 12:01:55 -04:00
/*! \brief Cached objectset for field and regex retrieval */
struct ast_variable * objectset ;
} ;
/*! \brief Structure used for fields comparison */
struct sorcery_memory_cache_fields_cmp_params {
/*! \brief Pointer to the sorcery structure */
const struct ast_sorcery * sorcery ;
/*! \brief The sorcery memory cache */
struct sorcery_memory_cache * cache ;
/*! \brief Pointer to the fields to check */
const struct ast_variable * fields ;
/*! \brief Regular expression for checking object id */
regex_t * regex ;
2017-11-09 09:21:38 -05:00
/*! \brief Prefix for matching object id */
const char * prefix ;
/*! \brief Prefix length in bytes for matching object id */
const size_t prefix_len ;
2015-12-05 12:01:55 -04:00
/*! \brief Optional container to put object into */
struct ao2_container * container ;
2015-05-16 19:02:50 -03:00
} ;
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 void sorcery_memory_cache_load ( void * data , const struct ast_sorcery * sorcery , const char * type ) ;
static void sorcery_memory_cache_reload ( void * data , const struct ast_sorcery * sorcery , const char * type ) ;
static void * sorcery_memory_cache_retrieve_id ( const struct ast_sorcery * sorcery , void * data , const char * type ,
const char * id ) ;
2015-12-05 12:01:55 -04:00
static void * sorcery_memory_cache_retrieve_fields ( const struct ast_sorcery * sorcery , void * data , const char * type ,
const struct ast_variable * fields ) ;
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 void sorcery_memory_cache_retrieve_regex ( const struct ast_sorcery * sorcery , void * data , const char * type ,
struct ao2_container * objects , const char * regex ) ;
2017-11-09 09:21:38 -05:00
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 ) ;
2015-05-16 19:02:50 -03:00
static int sorcery_memory_cache_delete ( const struct ast_sorcery * sorcery , void * data , void * object ) ;
static void sorcery_memory_cache_close ( void * data ) ;
static struct ast_sorcery_wizard memory_cache_object_wizard = {
. name = " memory_cache " ,
. open = sorcery_memory_cache_open ,
. create = sorcery_memory_cache_create ,
. update = sorcery_memory_cache_create ,
. delete = sorcery_memory_cache_delete ,
. load = sorcery_memory_cache_load ,
. reload = sorcery_memory_cache_reload ,
. retrieve_id = sorcery_memory_cache_retrieve_id ,
2015-12-05 12:01:55 -04:00
. retrieve_fields = sorcery_memory_cache_retrieve_fields ,
. retrieve_multiple = sorcery_memory_cache_retrieve_multiple ,
. retrieve_regex = sorcery_memory_cache_retrieve_regex ,
2017-11-09 09:21:38 -05:00
. retrieve_prefix = sorcery_memory_cache_retrieve_prefix ,
2015-05-16 19:02:50 -03:00
. close = sorcery_memory_cache_close ,
} ;
/*! \brief The bucket size for the container of caches */
# define CACHES_CONTAINER_BUCKET_SIZE 53
/*! \brief The default bucket size for the container of objects in the cache */
# define CACHE_CONTAINER_BUCKET_SIZE 53
2015-05-20 15:19:27 -05:00
/*! \brief Height of heap for cache object heap. Allows 31 initial objects */
# define CACHE_HEAP_INIT_HEIGHT 5
2015-05-16 19:02:50 -03:00
/*! \brief Container of created caches */
static struct ao2_container * caches ;
/*! \brief Scheduler for cache management */
static struct ast_sched_context * sched ;
2015-12-05 12:01:55 -04:00
# define PASSTHRU_UPDATE_THREAD_ID 0x5EED1E55
AST_THREADSTORAGE ( passthru_update_id_storage ) ;
2015-05-26 13:01:24 -05:00
2015-12-05 12:01:55 -04:00
static int is_passthru_update ( void )
2015-05-26 13:01:24 -05:00
{
2015-12-05 12:01:55 -04:00
uint32_t * passthru_update_thread_id ;
2015-05-26 13:01:24 -05:00
2015-12-05 12:01:55 -04:00
passthru_update_thread_id = ast_threadstorage_get ( & passthru_update_id_storage ,
sizeof ( * passthru_update_thread_id ) ) ;
if ( ! passthru_update_thread_id ) {
2015-05-26 13:01:24 -05:00
return 0 ;
}
2015-12-05 12:01:55 -04:00
return * passthru_update_thread_id = = PASSTHRU_UPDATE_THREAD_ID ;
2015-05-26 13:01:24 -05:00
}
2015-12-05 12:01:55 -04:00
static void set_passthru_update ( uint32_t value )
2015-05-26 13:01:24 -05:00
{
2015-12-05 12:01:55 -04:00
uint32_t * passthru_update_thread_id ;
2015-05-26 13:01:24 -05:00
2015-12-05 12:01:55 -04:00
passthru_update_thread_id = ast_threadstorage_get ( & passthru_update_id_storage ,
sizeof ( * passthru_update_thread_id ) ) ;
if ( ! passthru_update_thread_id ) {
ast_log ( LOG_ERROR , " Could not set passthru update ID for sorcery memory cache thread \n " ) ;
2015-05-26 13:01:24 -05:00
return ;
}
2015-12-05 12:01:55 -04:00
* passthru_update_thread_id = value ;
2015-05-26 13:01:24 -05:00
}
2015-12-05 12:01:55 -04:00
static void start_passthru_update ( void )
2015-05-26 13:01:24 -05:00
{
2015-12-05 12:01:55 -04:00
set_passthru_update ( PASSTHRU_UPDATE_THREAD_ID ) ;
}
2015-05-26 13:01:24 -05:00
2015-12-05 12:01:55 -04:00
static void end_passthru_update ( void )
{
set_passthru_update ( 0 ) ;
2015-05-26 13:01:24 -05:00
}
2015-05-16 19:02:50 -03:00
/*!
* \internal
* \brief Hashing function for the container holding caches
*
* \param obj A sorcery memory cache or name of one
* \param flags Hashing flags
*
* \return The hash of the memory cache name
*/
static int sorcery_memory_cache_hash ( const void * obj , int flags )
{
const struct sorcery_memory_cache * cache = obj ;
const char * name = obj ;
int hash ;
2015-09-30 17:27:23 -05:00
switch ( flags & OBJ_SEARCH_MASK ) {
2015-05-16 19:02:50 -03:00
default :
case OBJ_SEARCH_OBJECT :
name = cache - > name ;
/* Fall through */
case OBJ_SEARCH_KEY :
hash = ast_str_hash ( name ) ;
break ;
case OBJ_SEARCH_PARTIAL_KEY :
/* Should never happen in hash callback. */
ast_assert ( 0 ) ;
hash = 0 ;
break ;
}
return hash ;
}
/*!
* \internal
* \brief Comparison function for the container holding caches
*
* \param obj A sorcery memory cache
* \param arg A sorcery memory cache, or name of one
* \param flags Comparison flags
*
* \retval CMP_MATCH if the name is the same
* \retval 0 if the name does not match
*/
static int sorcery_memory_cache_cmp ( void * obj , void * arg , int flags )
{
const struct sorcery_memory_cache * left = obj ;
const struct sorcery_memory_cache * right = arg ;
const char * right_name = arg ;
int cmp ;
2015-09-30 17:27:23 -05:00
switch ( flags & OBJ_SEARCH_MASK ) {
2015-05-16 19:02:50 -03:00
default :
case OBJ_SEARCH_OBJECT :
right_name = right - > name ;
/* Fall through */
case OBJ_SEARCH_KEY :
cmp = strcmp ( left - > name , right_name ) ;
break ;
case OBJ_SEARCH_PARTIAL_KEY :
cmp = strncmp ( left - > name , right_name , strlen ( right_name ) ) ;
break ;
}
return cmp ? 0 : CMP_MATCH ;
}
/*!
* \internal
* \brief Hashing function for the container holding cached objects
*
* \param obj A cached object or id of one
* \param flags Hashing flags
*
* \return The hash of the cached object id
*/
static int sorcery_memory_cached_object_hash ( const void * obj , int flags )
{
const struct sorcery_memory_cached_object * cached = obj ;
const char * name = obj ;
int hash ;
2015-09-30 17:27:23 -05:00
switch ( flags & OBJ_SEARCH_MASK ) {
2015-05-16 19:02:50 -03:00
default :
case OBJ_SEARCH_OBJECT :
name = ast_sorcery_object_get_id ( cached - > object ) ;
/* Fall through */
case OBJ_SEARCH_KEY :
hash = ast_str_hash ( name ) ;
break ;
case OBJ_SEARCH_PARTIAL_KEY :
/* Should never happen in hash callback. */
ast_assert ( 0 ) ;
hash = 0 ;
break ;
}
return hash ;
}
/*!
* \internal
* \brief Comparison function for the container holding cached objects
*
* \param obj A cached object
* \param arg A cached object, or id of one
* \param flags Comparison flags
*
* \retval CMP_MATCH if the id is the same
* \retval 0 if the id does not match
*/
static int sorcery_memory_cached_object_cmp ( void * obj , void * arg , int flags )
{
struct sorcery_memory_cached_object * left = obj ;
struct sorcery_memory_cached_object * right = arg ;
const char * right_name = arg ;
int cmp ;
2015-09-30 17:27:23 -05:00
switch ( flags & OBJ_SEARCH_MASK ) {
2015-05-16 19:02:50 -03:00
default :
case OBJ_SEARCH_OBJECT :
right_name = ast_sorcery_object_get_id ( right - > object ) ;
/* Fall through */
case OBJ_SEARCH_KEY :
cmp = strcmp ( ast_sorcery_object_get_id ( left - > object ) , right_name ) ;
break ;
case OBJ_SEARCH_PARTIAL_KEY :
cmp = strncmp ( ast_sorcery_object_get_id ( left - > object ) , right_name , strlen ( right_name ) ) ;
break ;
}
return cmp ? 0 : CMP_MATCH ;
}
/*!
* \internal
* \brief Destructor function for a sorcery memory cache
*
* \param obj A sorcery memory cache
*/
static void sorcery_memory_cache_destructor ( void * obj )
{
struct sorcery_memory_cache * cache = obj ;
ast_free ( cache - > name ) ;
2015-05-20 15:19:27 -05:00
if ( cache - > object_heap ) {
ast_heap_destroy ( cache - > object_heap ) ;
}
2015-09-30 17:27:54 -05:00
ao2_cleanup ( cache - > objects ) ;
2015-12-05 12:01:55 -04:00
ast_free ( cache - > object_type ) ;
2015-05-16 19:02:50 -03:00
}
/*!
* \internal
* \brief Destructor function for sorcery memory cached objects
*
* \param obj A sorcery memory cached object
*/
static void sorcery_memory_cached_object_destructor ( void * obj )
{
struct sorcery_memory_cached_object * cached = obj ;
ao2_cleanup ( cached - > object ) ;
2015-12-05 12:01:55 -04:00
ast_variables_destroy ( cached - > objectset ) ;
2015-05-16 19:02:50 -03:00
}
2015-05-20 19:35:54 -03:00
static int schedule_cache_expiration ( struct sorcery_memory_cache * cache ) ;
2015-05-20 15:19:27 -05:00
/*!
* \internal
* \brief Remove an object from the cache.
*
* This removes the item from both the hashtable and the heap.
*
* \pre cache->objects is write-locked
*
* \param cache The cache from which the object is being removed.
* \param id The sorcery object id of the object to remove.
2015-05-20 19:35:54 -03:00
* \param reschedule Reschedule cache expiration if this was the oldest object.
2015-05-20 15:19:27 -05:00
*
* \retval 0 Success
* \retval non-zero Failure
*/
2015-05-20 19:35:54 -03:00
static int remove_from_cache ( struct sorcery_memory_cache * cache , const char * id , int reschedule )
2015-05-20 15:19:27 -05:00
{
struct sorcery_memory_cached_object * hash_object ;
2015-05-20 19:35:54 -03:00
struct sorcery_memory_cached_object * oldest_object ;
2015-05-20 15:19:27 -05:00
struct sorcery_memory_cached_object * heap_object ;
2015-09-30 17:27:54 -05:00
hash_object = ao2_find ( cache - > objects , id , OBJ_SEARCH_KEY | OBJ_UNLINK | OBJ_NOLOCK ) ;
2015-05-20 15:19:27 -05:00
if ( ! hash_object ) {
return - 1 ;
}
2015-06-02 12:20:00 -03:00
ast_assert ( ! strcmp ( ast_sorcery_object_get_id ( hash_object - > object ) , id ) ) ;
2015-05-20 19:35:54 -03:00
oldest_object = ast_heap_peek ( cache - > object_heap , 1 ) ;
2015-05-20 15:19:27 -05:00
heap_object = ast_heap_remove ( cache - > object_heap , hash_object ) ;
ast_assert ( heap_object = = hash_object ) ;
ao2_ref ( hash_object , - 1 ) ;
2015-05-20 19:35:54 -03:00
if ( reschedule & & ( oldest_object = = heap_object ) ) {
schedule_cache_expiration ( cache ) ;
}
return 0 ;
}
/*!
* \internal
* \brief Scheduler callback invoked to expire old objects
*
* \param data The opaque callback data (in our case, the memory cache)
*/
static int expire_objects_from_cache ( const void * data )
{
struct sorcery_memory_cache * cache = ( struct sorcery_memory_cache * ) data ;
struct sorcery_memory_cached_object * cached ;
2015-09-30 17:28:19 -05:00
/*
* We need to do deadlock avoidance between a non-scheduler thread
* blocking when trying to delete the scheduled entry for this
* callback because the scheduler thread is running this callback
* and this callback waiting for the cache->objects container lock
* that the blocked non-scheduler thread already holds.
*/
while ( ao2_trywrlock ( cache - > objects ) ) {
if ( cache - > del_expire ) {
cache - > expire_id = - 1 ;
ao2_ref ( cache , - 1 ) ;
return 0 ;
}
sched_yield ( ) ;
}
2015-05-20 19:35:54 -03:00
cache - > expire_id = - 1 ;
2015-09-30 17:27:54 -05:00
/* This is an optimization for objects which have been cached close to each other */
2015-05-20 19:35:54 -03:00
while ( ( cached = ast_heap_peek ( cache - > object_heap , 1 ) ) ) {
int expiration ;
expiration = ast_tvdiff_ms ( ast_tvadd ( cached - > created , ast_samp2tv ( cache - > object_lifetime_maximum , 1 ) ) , ast_tvnow ( ) ) ;
/* If the current oldest object has not yet expired stop and reschedule for it */
if ( expiration > 0 ) {
break ;
}
remove_from_cache ( cache , ast_sorcery_object_get_id ( cached - > object ) , 0 ) ;
}
schedule_cache_expiration ( cache ) ;
ao2_unlock ( cache - > objects ) ;
ao2_ref ( cache , - 1 ) ;
return 0 ;
}
2015-05-26 09:34:47 -03:00
/*!
* \internal
* \brief Remove all objects from the cache.
*
* This removes ALL objects from both the hash table and heap.
*
* \pre cache->objects is write-locked
*
* \param cache The cache to empty.
*/
static void remove_all_from_cache ( struct sorcery_memory_cache * cache )
{
2015-09-30 17:27:54 -05:00
while ( ast_heap_pop ( cache - > object_heap ) ) {
}
2015-05-26 09:34:47 -03:00
ao2_callback ( cache - > objects , OBJ_UNLINK | OBJ_NOLOCK | OBJ_NODATA | OBJ_MULTIPLE ,
NULL , NULL ) ;
2015-09-30 17:28:19 -05:00
cache - > del_expire = 1 ;
2015-05-26 09:34:47 -03:00
AST_SCHED_DEL_UNREF ( sched , cache - > expire_id , ao2_ref ( cache , - 1 ) ) ;
2015-09-30 17:28:19 -05:00
cache - > del_expire = 0 ;
2015-05-26 09:34:47 -03:00
}
/*!
* \internal
* \brief AO2 callback function for making an object stale immediately
*
* This changes the creation time of an object so it appears as though it is stale immediately.
*
* \param obj The cached object
* \param arg The cache itself
* \param flags Unused flags
*/
static int object_stale_callback ( void * obj , void * arg , int flags )
{
struct sorcery_memory_cached_object * cached = obj ;
struct sorcery_memory_cache * cache = arg ;
/* Since our granularity is seconds it's possible for something to retrieve us within a window
* where we wouldn't be treated as stale. To ensure that doesn't happen we use the configured stale
* time plus a second.
*/
cached - > created = ast_tvsub ( cached - > created , ast_samp2tv ( cache - > object_lifetime_stale + 1 , 1 ) ) ;
return CMP_MATCH ;
}
/*!
* \internal
* \brief Mark an object as stale explicitly.
*
* This changes the creation time of an object so it appears as though it is stale immediately.
*
* \pre cache->objects is read-locked
*
* \param cache The cache the object is in
* \param id The unique identifier of the object
*
* \retval 0 success
* \retval -1 failure
*/
static int mark_object_as_stale_in_cache ( struct sorcery_memory_cache * cache , const char * id )
{
struct sorcery_memory_cached_object * cached ;
cached = ao2_find ( cache - > objects , id , OBJ_SEARCH_KEY | OBJ_NOLOCK ) ;
if ( ! cached ) {
return - 1 ;
}
2015-06-02 12:20:00 -03:00
ast_assert ( ! strcmp ( ast_sorcery_object_get_id ( cached - > object ) , id ) ) ;
2015-05-26 09:34:47 -03:00
object_stale_callback ( cached , cache , 0 ) ;
ao2_ref ( cached , - 1 ) ;
return 0 ;
}
/*!
* \internal
* \brief Mark all objects as stale within a cache.
*
* This changes the creation time of ALL objects so they appear as though they are stale.
*
* \pre cache->objects is read-locked
*
* \param cache
*/
static void mark_all_as_stale_in_cache ( struct sorcery_memory_cache * cache )
{
ao2_callback ( cache - > objects , OBJ_NOLOCK | OBJ_NODATA | OBJ_MULTIPLE , object_stale_callback , cache ) ;
}
2015-05-20 19:35:54 -03:00
/*!
* \internal
* \brief Schedule a callback for cached object expiration.
*
* \pre cache->objects is write-locked
*
* \param cache The cache that is having its callback scheduled.
*
* \retval 0 success
* \retval -1 failure
*/
static int schedule_cache_expiration ( struct sorcery_memory_cache * cache )
{
struct sorcery_memory_cached_object * cached ;
int expiration = 0 ;
if ( ! cache - > object_lifetime_maximum ) {
return 0 ;
}
2015-09-30 17:28:19 -05:00
cache - > del_expire = 1 ;
AST_SCHED_DEL_UNREF ( sched , cache - > expire_id , ao2_ref ( cache , - 1 ) ) ;
cache - > del_expire = 0 ;
2015-05-20 19:35:54 -03:00
cached = ast_heap_peek ( cache - > object_heap , 1 ) ;
if ( ! cached ) {
# ifdef TEST_FRAMEWORK
ast_mutex_lock ( & cache - > lock ) ;
cache - > cache_completed = 1 ;
ast_cond_signal ( & cache - > cond ) ;
ast_mutex_unlock ( & cache - > lock ) ;
# endif
return 0 ;
}
expiration = MAX ( ast_tvdiff_ms ( ast_tvadd ( cached - > created , ast_samp2tv ( cache - > object_lifetime_maximum , 1 ) ) , ast_tvnow ( ) ) ,
1 ) ;
cache - > expire_id = ast_sched_add ( sched , expiration , expire_objects_from_cache , ao2_bump ( cache ) ) ;
if ( cache - > expire_id < 0 ) {
ao2_ref ( cache , - 1 ) ;
return - 1 ;
}
2015-05-20 15:19:27 -05:00
return 0 ;
}
/*!
* \internal
* \brief Remove the oldest item from the cache.
*
* \pre cache->objects is write-locked
*
* \param cache The cache from which to remove the oldest object
*
* \retval 0 Success
* \retval non-zero Failure
*/
static int remove_oldest_from_cache ( struct sorcery_memory_cache * cache )
{
struct sorcery_memory_cached_object * heap_old_object ;
struct sorcery_memory_cached_object * hash_old_object ;
heap_old_object = ast_heap_pop ( cache - > object_heap ) ;
if ( ! heap_old_object ) {
return - 1 ;
}
hash_old_object = ao2_find ( cache - > objects , heap_old_object ,
OBJ_SEARCH_OBJECT | OBJ_UNLINK | OBJ_NOLOCK ) ;
ast_assert ( heap_old_object = = hash_old_object ) ;
ao2_ref ( hash_old_object , - 1 ) ;
2015-05-20 19:35:54 -03:00
schedule_cache_expiration ( cache ) ;
2015-05-20 15:19:27 -05:00
return 0 ;
}
/*!
* \internal
* \brief Add a new object to the cache.
*
* \pre cache->objects is write-locked
*
* \param cache The cache in which to add the new object
* \param cached_object The object to add to the cache
*
* \retval 0 Success
* \retval non-zero Failure
*/
static int add_to_cache ( struct sorcery_memory_cache * cache ,
struct sorcery_memory_cached_object * cached_object )
{
2015-12-05 12:01:55 -04:00
struct sorcery_memory_cached_object * front ;
2015-05-20 15:19:27 -05:00
if ( ! ao2_link_flags ( cache - > objects , cached_object , OBJ_NOLOCK ) ) {
return - 1 ;
}
2015-05-20 19:35:54 -03:00
2015-12-05 12:01:55 -04:00
if ( cache - > full_backend_cache & & ( front = ast_heap_peek ( cache - > object_heap , 1 ) ) ) {
/* For a full backend cache all objects share the same lifetime */
cached_object - > created = front - > created ;
}
2015-05-20 15:19:27 -05:00
if ( ast_heap_push ( cache - > object_heap , cached_object ) ) {
ao2_find ( cache - > objects , cached_object ,
OBJ_SEARCH_OBJECT | OBJ_UNLINK | OBJ_NODATA | OBJ_NOLOCK ) ;
return - 1 ;
}
2015-05-20 19:35:54 -03:00
if ( cache - > expire_id = = - 1 ) {
schedule_cache_expiration ( cache ) ;
}
2015-05-20 15:19:27 -05:00
return 0 ;
}
2015-12-05 12:01:55 -04:00
/*!
* \internal
* \brief Allocate a cached object for caching an object
*
* \param sorcery The sorcery instance
* \param cache The sorcery memory cache
* \param object The object to cache
*
* \retval non-NULL success
* \retval NULL failure
*/
static struct sorcery_memory_cached_object * sorcery_memory_cached_object_alloc ( const struct ast_sorcery * sorcery ,
const struct sorcery_memory_cache * cache , void * object )
{
struct sorcery_memory_cached_object * cached ;
cached = ao2_alloc ( sizeof ( * cached ) , sorcery_memory_cached_object_destructor ) ;
if ( ! cached ) {
return NULL ;
}
cached - > object = ao2_bump ( object ) ;
cached - > created = ast_tvnow ( ) ;
cached - > stale_update_sched_id = - 1 ;
if ( cache - > full_backend_cache ) {
/* A cached objectset allows us to easily perform all retrieval operations in a
* minimal of time.
*/
cached - > objectset = ast_sorcery_objectset_create ( sorcery , object ) ;
if ( ! cached - > objectset ) {
ao2_ref ( cached , - 1 ) ;
return NULL ;
}
}
return cached ;
}
2015-05-16 19:02:50 -03:00
/*!
* \internal
* \brief Callback function to cache an object in a memory cache
*
* \param sorcery The sorcery instance
* \param data The sorcery memory cache
* \param object The object to cache
*
* \retval 0 success
* \retval -1 failure
*/
static int sorcery_memory_cache_create ( const struct ast_sorcery * sorcery , void * data , void * object )
{
struct sorcery_memory_cache * cache = data ;
struct sorcery_memory_cached_object * cached ;
2015-12-05 12:01:55 -04:00
cached = sorcery_memory_cached_object_alloc ( sorcery , cache , object ) ;
2015-05-16 19:02:50 -03:00
if ( ! cached ) {
return - 1 ;
}
/* As there is no guarantee that this won't be called by multiple threads wanting to cache
* the same object we remove any old ones, which turns this into a create/update function
* in reality. As well since there's no guarantee that the object in the cache is the same
* one here we remove any old objects using the object identifier.
*/
ao2_wrlock ( cache - > objects ) ;
2015-05-20 19:35:54 -03:00
remove_from_cache ( cache , ast_sorcery_object_get_id ( object ) , 1 ) ;
2015-05-20 15:19:27 -05:00
if ( cache - > maximum_objects & & ao2_container_count ( cache - > objects ) > = cache - > maximum_objects ) {
if ( remove_oldest_from_cache ( cache ) ) {
ast_log ( LOG_ERROR , " Unable to make room in cache for sorcery object '%s'. \n " ,
ast_sorcery_object_get_id ( object ) ) ;
ao2_unlock ( cache - > objects ) ;
2015-09-30 17:27:54 -05:00
ao2_ref ( cached , - 1 ) ;
2015-05-20 15:19:27 -05:00
return - 1 ;
}
2015-06-02 12:20:00 -03:00
ast_assert ( ao2_container_count ( cache - > objects ) ! = cache - > maximum_objects ) ;
2015-05-20 15:19:27 -05:00
}
if ( add_to_cache ( cache , cached ) ) {
ast_log ( LOG_ERROR , " Unable to add object '%s' to the cache \n " ,
ast_sorcery_object_get_id ( object ) ) ;
ao2_unlock ( cache - > objects ) ;
2015-09-30 17:27:54 -05:00
ao2_ref ( cached , - 1 ) ;
2015-05-20 15:19:27 -05:00
return - 1 ;
}
2015-05-16 19:02:50 -03:00
ao2_unlock ( cache - > objects ) ;
ao2_ref ( cached , - 1 ) ;
return 0 ;
}
2015-12-05 12:01:55 -04:00
/*!
* \internal
* \brief AO2 callback function for adding an object to a memory cache
*
* \param obj The cached object
* \param arg The sorcery instance
* \param data The cache itself
* \param flags Unused flags
*/
static int object_add_to_cache_callback ( void * obj , void * arg , void * data , int flags )
{
struct sorcery_memory_cache * cache = data ;
struct sorcery_memory_cached_object * cached ;
cached = sorcery_memory_cached_object_alloc ( arg , cache , obj ) ;
if ( ! cached ) {
return CMP_STOP ;
}
add_to_cache ( cache , cached ) ;
ao2_ref ( cached , - 1 ) ;
return 0 ;
}
struct stale_cache_update_task_data {
struct ast_sorcery * sorcery ;
struct sorcery_memory_cache * cache ;
char * type ;
} ;
static void stale_cache_update_task_data_destructor ( void * obj )
{
struct stale_cache_update_task_data * task_data = obj ;
ao2_cleanup ( task_data - > cache ) ;
ast_sorcery_unref ( task_data - > sorcery ) ;
ast_free ( task_data - > type ) ;
}
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 )
{
struct stale_cache_update_task_data * task_data ;
task_data = ao2_alloc_options ( sizeof ( * task_data ) , stale_cache_update_task_data_destructor ,
AO2_ALLOC_OPT_LOCK_NOLOCK ) ;
if ( ! task_data ) {
return NULL ;
}
task_data - > sorcery = ao2_bump ( sorcery ) ;
task_data - > cache = ao2_bump ( cache ) ;
task_data - > type = ast_strdup ( type ) ;
if ( ! task_data - > type ) {
ao2_ref ( task_data , - 1 ) ;
return NULL ;
}
return task_data ;
}
static int stale_cache_update ( const void * data )
{
struct stale_cache_update_task_data * task_data = ( struct stale_cache_update_task_data * ) data ;
struct ao2_container * backend_objects ;
start_passthru_update ( ) ;
backend_objects = ast_sorcery_retrieve_by_fields ( task_data - > sorcery , task_data - > type ,
AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL , NULL ) ;
end_passthru_update ( ) ;
if ( ! backend_objects ) {
task_data - > cache - > stale_update_sched_id = - 1 ;
ao2_ref ( task_data , - 1 ) ;
return 0 ;
}
if ( task_data - > cache - > maximum_objects & & ao2_container_count ( backend_objects ) > = task_data - > cache - > maximum_objects ) {
ast_log ( LOG_ERROR , " The backend contains %d objects while the sorcery memory cache '%s' is explicitly configured to only allow %d \n " ,
ao2_container_count ( backend_objects ) , task_data - > cache - > name , task_data - > cache - > maximum_objects ) ;
task_data - > cache - > stale_update_sched_id = - 1 ;
ao2_ref ( task_data , - 1 ) ;
return 0 ;
}
ao2_wrlock ( task_data - > cache - > objects ) ;
remove_all_from_cache ( task_data - > cache ) ;
ao2_callback_data ( backend_objects , OBJ_NOLOCK | OBJ_NODATA | OBJ_MULTIPLE , object_add_to_cache_callback ,
task_data - > sorcery , task_data - > cache ) ;
/* If the number of cached objects does not match the number of backend objects we encountered a memory allocation
* failure and the cache is incomplete, so drop everything and fall back to querying the backend directly
* as it may be able to provide what is wanted.
*/
if ( ao2_container_count ( task_data - > cache - > objects ) ! = ao2_container_count ( backend_objects ) ) {
ast_log ( LOG_WARNING , " The backend contains %d objects while only %d could be added to sorcery memory cache '%s' \n " ,
ao2_container_count ( backend_objects ) , ao2_container_count ( task_data - > cache - > objects ) , task_data - > cache - > name ) ;
remove_all_from_cache ( task_data - > cache ) ;
}
ao2_unlock ( task_data - > cache - > objects ) ;
ao2_ref ( backend_objects , - 1 ) ;
task_data - > cache - > stale_update_sched_id = - 1 ;
ao2_ref ( task_data , - 1 ) ;
return 0 ;
}
2015-05-26 13:01:24 -05:00
struct stale_update_task_data {
struct ast_sorcery * sorcery ;
struct sorcery_memory_cache * cache ;
void * object ;
} ;
static void stale_update_task_data_destructor ( void * obj )
{
struct stale_update_task_data * task_data = obj ;
ao2_cleanup ( task_data - > cache ) ;
ao2_cleanup ( task_data - > object ) ;
ast_sorcery_unref ( task_data - > sorcery ) ;
}
static struct stale_update_task_data * stale_update_task_data_alloc ( struct ast_sorcery * sorcery ,
struct sorcery_memory_cache * cache , const char * type , void * object )
{
struct stale_update_task_data * task_data ;
task_data = ao2_alloc_options ( sizeof ( * task_data ) , stale_update_task_data_destructor ,
AO2_ALLOC_OPT_LOCK_NOLOCK ) ;
if ( ! task_data ) {
return NULL ;
}
task_data - > sorcery = ao2_bump ( sorcery ) ;
task_data - > cache = ao2_bump ( cache ) ;
task_data - > object = ao2_bump ( object ) ;
return task_data ;
}
static int stale_item_update ( const void * data )
{
struct stale_update_task_data * task_data = ( struct stale_update_task_data * ) data ;
void * object ;
2015-12-05 12:01:55 -04:00
start_passthru_update ( ) ;
2015-05-26 13:01:24 -05:00
object = ast_sorcery_retrieve_by_id ( task_data - > sorcery ,
ast_sorcery_object_get_type ( task_data - > object ) ,
ast_sorcery_object_get_id ( task_data - > object ) ) ;
if ( ! object ) {
ast_debug ( 1 , " Backend no longer has object type '%s' ID '%s'. Removing from cache \n " ,
ast_sorcery_object_get_type ( task_data - > object ) ,
ast_sorcery_object_get_id ( task_data - > object ) ) ;
sorcery_memory_cache_delete ( task_data - > sorcery , task_data - > cache ,
task_data - > object ) ;
} else {
ast_debug ( 1 , " Refreshing stale cache object type '%s' ID '%s' \n " ,
ast_sorcery_object_get_type ( task_data - > object ) ,
ast_sorcery_object_get_id ( task_data - > object ) ) ;
sorcery_memory_cache_create ( task_data - > sorcery , task_data - > cache ,
object ) ;
2019-09-23 11:01:36 -05:00
ao2_ref ( object , - 1 ) ;
2015-05-26 13:01:24 -05:00
}
2015-06-04 07:33:30 -03:00
ast_test_suite_event_notify ( " SORCERY_MEMORY_CACHE_REFRESHED " , " Cache: %s \r \n Type: %s \r \n Name: %s \r \n " ,
task_data - > cache - > name , ast_sorcery_object_get_type ( task_data - > object ) ,
ast_sorcery_object_get_id ( task_data - > object ) ) ;
2015-05-26 13:01:24 -05:00
ao2_ref ( task_data , - 1 ) ;
2015-12-05 12:01:55 -04:00
end_passthru_update ( ) ;
2015-05-26 13:01:24 -05:00
return 0 ;
}
2015-12-05 12:01:55 -04:00
/*!
* \internal
* \brief Populate the cache with all objects from the backend
*
* \pre cache->objects is write-locked
*
* \param sorcery The sorcery instance
* \param type The type of object
* \param cache The sorcery memory cache
*/
static void memory_cache_populate ( const struct ast_sorcery * sorcery , const char * type , struct sorcery_memory_cache * cache )
{
struct ao2_container * backend_objects ;
start_passthru_update ( ) ;
backend_objects = ast_sorcery_retrieve_by_fields ( sorcery , type , AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL , NULL ) ;
end_passthru_update ( ) ;
if ( ! backend_objects ) {
/* This will occur in off-nominal memory allocation failure scenarios */
return ;
}
if ( cache - > maximum_objects & & ao2_container_count ( backend_objects ) > = cache - > maximum_objects ) {
ast_log ( LOG_ERROR , " The backend contains %d objects while the sorcery memory cache '%s' is explicitly configured to only allow %d \n " ,
ao2_container_count ( backend_objects ) , cache - > name , cache - > maximum_objects ) ;
return ;
}
ao2_callback_data ( backend_objects , OBJ_NOLOCK | OBJ_NODATA | OBJ_MULTIPLE , object_add_to_cache_callback ,
( struct ast_sorcery * ) sorcery , cache ) ;
/* If the number of cached objects does not match the number of backend objects we encountered a memory allocation
* failure and the cache is incomplete, so drop everything and fall back to querying the backend directly
* as it may be able to provide what is wanted.
*/
if ( ao2_container_count ( cache - > objects ) ! = ao2_container_count ( backend_objects ) ) {
ast_log ( LOG_WARNING , " The backend contains %d objects while only %d could be added to sorcery memory cache '%s' \n " ,
ao2_container_count ( backend_objects ) , ao2_container_count ( cache - > objects ) , cache - > name ) ;
remove_all_from_cache ( cache ) ;
}
ao2_ref ( backend_objects , - 1 ) ;
}
/*!
* \internal
* \brief Determine if a full backend cache update is needed and do it
*
* \param sorcery The sorcery instance
* \param type The type of object
* \param cache The sorcery memory cache
*/
static void memory_cache_full_update ( const struct ast_sorcery * sorcery , const char * type , struct sorcery_memory_cache * cache )
{
if ( ! cache - > full_backend_cache ) {
return ;
}
ao2_wrlock ( cache - > objects ) ;
if ( ! ao2_container_count ( cache - > objects ) ) {
memory_cache_populate ( sorcery , type , cache ) ;
}
ao2_unlock ( cache - > objects ) ;
}
/*!
* \internal
* \brief Queue a full cache update
*
* \param sorcery The sorcery instance
* \param cache The sorcery memory cache
* \param type The type of object
*/
static void memory_cache_stale_update_full ( const struct ast_sorcery * sorcery , struct sorcery_memory_cache * cache ,
const char * type )
{
ao2_wrlock ( cache - > objects ) ;
if ( cache - > stale_update_sched_id = = - 1 ) {
struct stale_cache_update_task_data * task_data ;
task_data = stale_cache_update_task_data_alloc ( ( struct ast_sorcery * ) sorcery ,
cache , type ) ;
if ( task_data ) {
cache - > stale_update_sched_id = ast_sched_add ( sched , 1 ,
stale_cache_update , task_data ) ;
}
if ( cache - > stale_update_sched_id < 0 ) {
ao2_cleanup ( task_data ) ;
}
}
ao2_unlock ( cache - > objects ) ;
}
/*!
* \internal
* \brief Queue a stale object update
*
* \param sorcery The sorcery instance
* \param cache The sorcery memory cache
* \param cached The cached object
*/
static void memory_cache_stale_update_object ( const struct ast_sorcery * sorcery , struct sorcery_memory_cache * cache ,
struct sorcery_memory_cached_object * cached )
{
ao2_lock ( cached ) ;
if ( cached - > stale_update_sched_id = = - 1 ) {
struct stale_update_task_data * task_data ;
task_data = stale_update_task_data_alloc ( ( struct ast_sorcery * ) sorcery ,
cache , ast_sorcery_object_get_type ( cached - > object ) , cached - > object ) ;
if ( task_data ) {
ast_debug ( 1 , " Cached sorcery object type '%s' ID '%s' is stale. Refreshing \n " ,
ast_sorcery_object_get_type ( cached - > object ) , ast_sorcery_object_get_id ( cached - > object ) ) ;
cached - > stale_update_sched_id = ast_sched_add ( sched , 1 ,
stale_item_update , task_data ) ;
}
if ( cached - > stale_update_sched_id < 0 ) {
ao2_cleanup ( task_data ) ;
ast_log ( LOG_ERROR , " Unable to update stale cached object type '%s', ID '%s'. \n " ,
ast_sorcery_object_get_type ( cached - > object ) , ast_sorcery_object_get_id ( cached - > object ) ) ;
}
}
ao2_unlock ( cached ) ;
}
/*!
* \internal
* \brief Check whether an object (or cache) is stale and queue an update
*
* \param sorcery The sorcery instance
* \param cache The sorcery memory cache
* \param cached The cached object
*/
static void memory_cache_stale_check_object ( const struct ast_sorcery * sorcery , struct sorcery_memory_cache * cache ,
struct sorcery_memory_cached_object * cached )
{
struct timeval elapsed ;
if ( ! cache - > object_lifetime_stale ) {
return ;
}
/* For a full cache as every object has the same expiration/staleness we can do the same check */
elapsed = ast_tvsub ( ast_tvnow ( ) , cached - > created ) ;
if ( elapsed . tv_sec < cache - > object_lifetime_stale ) {
return ;
}
if ( cache - > full_backend_cache ) {
memory_cache_stale_update_full ( sorcery , cache , ast_sorcery_object_get_type ( cached - > object ) ) ;
} else {
memory_cache_stale_update_object ( sorcery , cache , cached ) ;
}
}
/*!
* \internal
* \brief Check whether the entire cache is stale or not and queue an update
*
* \param sorcery The sorcery instance
* \param cache The sorcery memory cache
*
* \note Unlike \ref memory_cache_stale_check this does not require an explicit object
*/
static void memory_cache_stale_check ( const struct ast_sorcery * sorcery , struct sorcery_memory_cache * cache )
{
struct sorcery_memory_cached_object * cached ;
ao2_rdlock ( cache - > objects ) ;
cached = ao2_bump ( ast_heap_peek ( cache - > object_heap , 1 ) ) ;
ao2_unlock ( cache - > objects ) ;
if ( ! cached ) {
return ;
}
memory_cache_stale_check_object ( sorcery , cache , cached ) ;
ao2_ref ( cached , - 1 ) ;
}
2015-05-16 19:02:50 -03:00
/*!
* \internal
* \brief Callback function to retrieve an object from a memory cache
*
* \param sorcery The sorcery instance
* \param data The sorcery memory cache
* \param type The type of the object to retrieve
* \param id The id of the object to retrieve
*
* \retval non-NULL success
* \retval NULL failure
*/
static void * sorcery_memory_cache_retrieve_id ( const struct ast_sorcery * sorcery , void * data , const char * type , const char * id )
{
struct sorcery_memory_cache * cache = data ;
struct sorcery_memory_cached_object * cached ;
void * object ;
2015-12-05 12:01:55 -04:00
if ( is_passthru_update ( ) ) {
2015-05-26 13:01:24 -05:00
return NULL ;
}
2015-12-05 12:01:55 -04:00
memory_cache_full_update ( sorcery , type , cache ) ;
2015-05-16 19:02:50 -03:00
cached = ao2_find ( cache - > objects , id , OBJ_SEARCH_KEY ) ;
if ( ! cached ) {
return NULL ;
}
2015-06-02 12:20:00 -03:00
ast_assert ( ! strcmp ( ast_sorcery_object_get_id ( cached - > object ) , id ) ) ;
2015-12-05 12:01:55 -04:00
memory_cache_stale_check_object ( sorcery , cache , cached ) ;
2015-05-26 13:01:24 -05:00
2015-05-16 19:02:50 -03:00
object = ao2_bump ( cached - > object ) ;
ao2_ref ( cached , - 1 ) ;
return object ;
}
2015-12-05 12:01:55 -04:00
/*!
* \internal
* \brief AO2 callback function for comparing a retrieval request and finding applicable objects
*
* \param obj The cached object
* \param arg The comparison parameters
* \param flags Unused flags
*/
static int sorcery_memory_cache_fields_cmp ( void * obj , void * arg , int flags )
{
struct sorcery_memory_cached_object * cached = obj ;
const struct sorcery_memory_cache_fields_cmp_params * params = arg ;
RAII_VAR ( struct ast_variable * , diff , NULL , ast_variables_destroy ) ;
if ( params - > regex ) {
/* If a regular expression has been provided see if it matches, otherwise move on */
if ( ! regexec ( params - > regex , ast_sorcery_object_get_id ( cached - > object ) , 0 , NULL , 0 ) ) {
ao2_link ( params - > container , cached - > object ) ;
}
return 0 ;
2017-11-09 09:21:38 -05:00
} else if ( params - > prefix ) {
if ( ! strncmp ( params - > prefix , ast_sorcery_object_get_id ( cached - > object ) , params - > prefix_len ) ) {
ao2_link ( params - > container , cached - > object ) ;
}
return 0 ;
2015-12-05 12:01:55 -04:00
} else if ( params - > fields & &
2016-03-08 14:55:30 -07:00
( ! ast_variable_lists_match ( cached - > objectset , params - > fields , 0 ) ) ) {
2015-12-05 12:01:55 -04:00
/* If we can't turn the object into an object set OR if differences exist between the fields
* passed in and what are present on the object they are not a match.
*/
return 0 ;
}
if ( params - > container ) {
ao2_link ( params - > container , cached - > object ) ;
/* As multiple objects are being returned keep going */
return 0 ;
} else {
/* Immediately stop and return, we only want a single object */
return CMP_MATCH | CMP_STOP ;
}
}
/*!
* \internal
* \brief Callback function to retrieve a single object based on fields
*
* \param sorcery The sorcery instance
* \param data The sorcery memory cache
* \param type The type of the object to retrieve
* \param fields Any explicit fields to search for
*/
static void * sorcery_memory_cache_retrieve_fields ( const struct ast_sorcery * sorcery , void * data , const char * type ,
const struct ast_variable * fields )
{
struct sorcery_memory_cache * cache = data ;
struct sorcery_memory_cache_fields_cmp_params params = {
. sorcery = sorcery ,
. cache = cache ,
. fields = fields ,
} ;
struct sorcery_memory_cached_object * cached ;
void * object = NULL ;
if ( is_passthru_update ( ) | | ! cache - > full_backend_cache | | ! fields ) {
return NULL ;
}
cached = ao2_callback ( cache - > objects , 0 , sorcery_memory_cache_fields_cmp , & params ) ;
if ( cached ) {
memory_cache_stale_check_object ( sorcery , cache , cached ) ;
object = ao2_bump ( cached - > object ) ;
ao2_ref ( cached , - 1 ) ;
}
return object ;
}
/*!
* \internal
* \brief Callback function to retrieve multiple objects from a memory cache
*
* \param sorcery The sorcery instance
* \param data The sorcery memory cache
* \param type The type of the object to retrieve
* \param objects Container to place the objects into
* \param fields Any explicit fields to search for
*/
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 )
{
struct sorcery_memory_cache * cache = data ;
struct sorcery_memory_cache_fields_cmp_params params = {
. sorcery = sorcery ,
. cache = cache ,
. fields = fields ,
. container = objects ,
} ;
if ( is_passthru_update ( ) | | ! cache - > full_backend_cache ) {
return ;
}
memory_cache_full_update ( sorcery , type , cache ) ;
ao2_callback ( cache - > objects , 0 , sorcery_memory_cache_fields_cmp , & params ) ;
if ( ao2_container_count ( objects ) ) {
memory_cache_stale_check ( sorcery , cache ) ;
}
}
/*!
* \internal
* \brief Callback function to retrieve multiple objects using a regex on the object id
*
* \param sorcery The sorcery instance
* \param data The sorcery memory cache
* \param type The type of the object to retrieve
* \param objects Container to place the objects into
* \param regex Regular expression to apply to the object id
*/
static void sorcery_memory_cache_retrieve_regex ( const struct ast_sorcery * sorcery , void * data , const char * type ,
struct ao2_container * objects , const char * regex )
{
struct sorcery_memory_cache * cache = data ;
regex_t expression ;
struct sorcery_memory_cache_fields_cmp_params params = {
. sorcery = sorcery ,
. cache = cache ,
. container = objects ,
. regex = & expression ,
} ;
if ( is_passthru_update ( ) | | ! cache - > full_backend_cache | | regcomp ( & expression , regex , REG_EXTENDED | REG_NOSUB ) ) {
return ;
}
memory_cache_full_update ( sorcery , type , cache ) ;
ao2_callback ( cache - > objects , 0 , sorcery_memory_cache_fields_cmp , & params ) ;
regfree ( & expression ) ;
if ( ao2_container_count ( objects ) ) {
memory_cache_stale_check ( sorcery , cache ) ;
}
}
2017-11-09 09:21:38 -05:00
/*!
* \internal
* \brief Callback function to retrieve multiple objects whose id matches a prefix
*
* \param sorcery The sorcery instance
* \param data The sorcery memory cache
* \param type The type of the object to retrieve
* \param objects Container to place the objects into
* \param prefix Prefix to match against the object id
*/
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 )
{
struct sorcery_memory_cache * cache = data ;
struct sorcery_memory_cache_fields_cmp_params params = {
. sorcery = sorcery ,
. cache = cache ,
. container = objects ,
. prefix = prefix ,
. prefix_len = prefix_len ,
} ;
if ( is_passthru_update ( ) | | ! cache - > full_backend_cache ) {
return ;
}
memory_cache_full_update ( sorcery , type , cache ) ;
ao2_callback ( cache - > objects , 0 , sorcery_memory_cache_fields_cmp , & params ) ;
if ( ao2_container_count ( objects ) ) {
memory_cache_stale_check ( sorcery , cache ) ;
}
}
2015-05-16 19:02:50 -03:00
/*!
* \internal
2015-06-17 09:00:21 -03:00
* \brief Callback function to finish configuring the memory cache
2015-05-16 19:02:50 -03:00
*
* \param data The sorcery memory cache
* \param sorcery The sorcery instance
* \param type The type of object being loaded
*/
static void sorcery_memory_cache_load ( void * data , const struct ast_sorcery * sorcery , const char * type )
{
struct sorcery_memory_cache * cache = data ;
/* If no name was explicitly specified generate one given the sorcery instance and object type */
if ( ast_strlen_zero ( cache - > name ) ) {
ast_asprintf ( & cache - > name , " %s/%s " , ast_sorcery_get_module ( sorcery ) , type ) ;
}
ao2_link ( caches , cache ) ;
ast_debug ( 1 , " Memory cache '%s' associated with sorcery instance '%p' of module '%s' with object type '%s' \n " ,
cache - > name , sorcery , ast_sorcery_get_module ( sorcery ) , type ) ;
2015-12-05 12:01:55 -04:00
2017-01-25 15:26:53 -06:00
cache - > sorcery = sorcery ;
cache - > object_type = ast_strdup ( type ) ;
2015-05-16 19:02:50 -03:00
}
/*!
* \internal
* \brief Callback function to expire objects from the memory cache on reload (if configured)
*
* \param data The sorcery memory cache
* \param sorcery The sorcery instance
* \param type The type of object being reloaded
*/
static void sorcery_memory_cache_reload ( void * data , const struct ast_sorcery * sorcery , const char * type )
{
2015-06-04 15:11:44 -03:00
struct sorcery_memory_cache * cache = data ;
if ( ! cache - > expire_on_reload ) {
return ;
}
ao2_wrlock ( cache - > objects ) ;
remove_all_from_cache ( cache ) ;
ao2_unlock ( cache - > objects ) ;
2015-05-16 19:02:50 -03:00
}
/*!
* \internal
* \brief Function used to take an unsigned integer based configuration option and parse it
*
* \param value The string value of the configuration option
* \param result The unsigned integer to place the result in
*
* \retval 0 failure
* \retval 1 success
*/
static int configuration_parse_unsigned_integer ( const char * value , unsigned int * result )
{
if ( ast_strlen_zero ( value ) | | ! strncmp ( value , " - " , 1 ) ) {
return 0 ;
}
return sscanf ( value , " %30u " , result ) ;
}
2015-05-20 15:19:27 -05:00
static int age_cmp ( void * a , void * b )
{
return ast_tvcmp ( ( ( struct sorcery_memory_cached_object * ) b ) - > created ,
( ( struct sorcery_memory_cached_object * ) a ) - > created ) ;
}
2015-05-16 19:02:50 -03:00
/*!
* \internal
* \brief Callback function to create a new sorcery memory cache using provided configuration
*
* \param data A stringified configuration for the memory cache
*
* \retval non-NULL success
* \retval NULL failure
*/
static void * sorcery_memory_cache_open ( const char * data )
{
char * options = ast_strdup ( data ) , * option ;
RAII_VAR ( struct sorcery_memory_cache * , cache , NULL , ao2_cleanup ) ;
cache = ao2_alloc_options ( sizeof ( * cache ) , sorcery_memory_cache_destructor , AO2_ALLOC_OPT_LOCK_NOLOCK ) ;
if ( ! cache ) {
return NULL ;
}
2015-05-20 19:35:54 -03:00
cache - > expire_id = - 1 ;
2015-12-05 12:01:55 -04:00
cache - > stale_update_sched_id = - 1 ;
2015-05-20 19:35:54 -03:00
2015-05-16 19:02:50 -03:00
/* If no configuration options have been provided this memory cache will operate in a default
* configuration.
*/
while ( ! ast_strlen_zero ( options ) & & ( option = strsep ( & options , " , " ) ) ) {
char * name = strsep ( & option , " = " ) , * value = option ;
if ( ! strcasecmp ( name , " name " ) ) {
if ( ast_strlen_zero ( value ) ) {
ast_log ( LOG_ERROR , " A name must be specified for the memory cache \n " ) ;
return NULL ;
}
ast_free ( cache - > name ) ;
cache - > name = ast_strdup ( value ) ;
} else if ( ! strcasecmp ( name , " maximum_objects " ) ) {
if ( configuration_parse_unsigned_integer ( value , & cache - > maximum_objects ) ! = 1 ) {
ast_log ( LOG_ERROR , " Unsupported maximum objects value of '%s' used for memory cache \n " ,
value ) ;
return NULL ;
}
} else if ( ! strcasecmp ( name , " object_lifetime_maximum " ) ) {
if ( configuration_parse_unsigned_integer ( value , & cache - > object_lifetime_maximum ) ! = 1 ) {
ast_log ( LOG_ERROR , " Unsupported object maximum lifetime value of '%s' used for memory cache \n " ,
value ) ;
return NULL ;
}
} else if ( ! strcasecmp ( name , " object_lifetime_stale " ) ) {
if ( configuration_parse_unsigned_integer ( value , & cache - > object_lifetime_stale ) ! = 1 ) {
ast_log ( LOG_ERROR , " Unsupported object stale lifetime value of '%s' used for memory cache \n " ,
value ) ;
return NULL ;
}
} else if ( ! strcasecmp ( name , " expire_on_reload " ) ) {
cache - > expire_on_reload = ast_true ( value ) ;
2015-12-05 12:01:55 -04:00
} else if ( ! strcasecmp ( name , " full_backend_cache " ) ) {
cache - > full_backend_cache = ast_true ( value ) ;
2015-05-16 19:02:50 -03:00
} else {
ast_log ( LOG_ERROR , " Unsupported option '%s' used for memory cache \n " , name ) ;
return NULL ;
}
}
2018-10-16 15:11:06 -04:00
cache - > objects = ao2_container_alloc_hash ( AO2_ALLOC_OPT_LOCK_RWLOCK , 0 ,
2015-05-16 19:02:50 -03:00
cache - > maximum_objects ? cache - > maximum_objects : CACHE_CONTAINER_BUCKET_SIZE ,
2018-10-16 15:11:06 -04:00
sorcery_memory_cached_object_hash , NULL , sorcery_memory_cached_object_cmp ) ;
2015-05-16 19:02:50 -03:00
if ( ! cache - > objects ) {
ast_log ( LOG_ERROR , " Could not create a container to hold cached objects for memory cache \n " ) ;
return NULL ;
}
2015-05-20 15:19:27 -05:00
cache - > object_heap = ast_heap_create ( CACHE_HEAP_INIT_HEIGHT , age_cmp ,
offsetof ( struct sorcery_memory_cached_object , __heap_index ) ) ;
if ( ! cache - > object_heap ) {
ast_log ( LOG_ERROR , " Could not create heap to hold cached objects \n " ) ;
return NULL ;
}
2015-05-16 19:02:50 -03:00
/* The memory cache is not linked to the caches container until the load callback is invoked.
* Linking occurs there so an intelligent cache name can be constructed using the module of
* the sorcery instance and the specific object type if no cache name was specified as part
* of the configuration.
*/
/* This is done as RAII_VAR will drop the reference */
return ao2_bump ( cache ) ;
}
/*!
* \internal
* \brief Callback function to delete an object from a memory cache
*
* \param sorcery The sorcery instance
* \param data The sorcery memory cache
* \param object The object to cache
*
* \retval 0 success
* \retval -1 failure
*/
static int sorcery_memory_cache_delete ( const struct ast_sorcery * sorcery , void * data , void * object )
{
struct sorcery_memory_cache * cache = data ;
2015-05-20 15:19:27 -05:00
int res ;
2015-05-16 19:02:50 -03:00
2015-05-20 15:19:27 -05:00
ao2_wrlock ( cache - > objects ) ;
2015-05-20 19:35:54 -03:00
res = remove_from_cache ( cache , ast_sorcery_object_get_id ( object ) , 1 ) ;
2015-05-20 15:19:27 -05:00
ao2_unlock ( cache - > objects ) ;
2015-05-16 19:02:50 -03:00
2015-05-20 15:19:27 -05:00
if ( res ) {
2016-12-13 13:06:34 -07:00
ast_debug ( 1 , " Unable to delete object '%s' from sorcery cache \n " , ast_sorcery_object_get_id ( object ) ) ;
2015-05-20 15:19:27 -05:00
}
return res ;
2015-05-16 19:02:50 -03:00
}
/*!
* \internal
* \brief Callback function to terminate a memory cache
*
* \param data The sorcery memory cache
*/
static void sorcery_memory_cache_close ( void * data )
{
struct sorcery_memory_cache * cache = data ;
/* This can occur if a cache is created but never loaded */
if ( ! ast_strlen_zero ( cache - > name ) ) {
ao2_unlink ( caches , cache ) ;
}
2015-05-20 19:35:54 -03:00
if ( cache - > object_lifetime_maximum ) {
/* If object lifetime support is enabled we need to explicitly drop all cached objects here
* and stop the scheduled task. Failure to do so could potentially keep the cache around for
* a prolonged period of time.
*/
ao2_wrlock ( cache - > objects ) ;
2015-10-01 14:30:06 -05:00
remove_all_from_cache ( cache ) ;
2015-05-20 19:35:54 -03:00
ao2_unlock ( cache - > objects ) ;
}
2015-12-05 12:01:55 -04:00
if ( cache - > full_backend_cache ) {
ao2_wrlock ( cache - > objects ) ;
cache - > sorcery = NULL ;
ao2_unlock ( cache - > objects ) ;
}
2015-05-16 19:02:50 -03:00
ao2_ref ( cache , - 1 ) ;
}
2015-05-26 09:34:47 -03:00
/*!
* \internal
* \brief CLI tab completion for cache names
*/
static char * sorcery_memory_cache_complete_name ( const char * word , int state )
{
struct sorcery_memory_cache * cache ;
struct ao2_iterator it_caches ;
int wordlen = strlen ( word ) ;
int which = 0 ;
char * result = NULL ;
it_caches = ao2_iterator_init ( caches , 0 ) ;
while ( ( cache = ao2_iterator_next ( & it_caches ) ) ) {
if ( ! strncasecmp ( word , cache - > name , wordlen )
& & + + which > state ) {
result = ast_strdup ( cache - > name ) ;
}
ao2_ref ( cache , - 1 ) ;
if ( result ) {
break ;
}
}
ao2_iterator_destroy ( & it_caches ) ;
return result ;
}
/*!
* \internal
* \brief CLI command implementation for 'sorcery memory cache show'
*/
static char * sorcery_memory_cache_show ( struct ast_cli_entry * e , int cmd , struct ast_cli_args * a )
{
struct sorcery_memory_cache * cache ;
switch ( cmd ) {
case CLI_INIT :
e - > command = " sorcery memory cache show " ;
e - > usage =
" Usage: sorcery memory cache show <name> \n "
" Show sorcery memory cache configuration and statistics. \n " ;
return NULL ;
case CLI_GENERATE :
if ( a - > pos = = 4 ) {
return sorcery_memory_cache_complete_name ( a - > word , a - > n ) ;
} else {
return NULL ;
}
}
if ( a - > argc ! = 5 ) {
return CLI_SHOWUSAGE ;
}
cache = ao2_find ( caches , a - > argv [ 4 ] , OBJ_SEARCH_KEY ) ;
if ( ! cache ) {
ast_cli ( a - > fd , " Specified sorcery memory cache '%s' does not exist \n " , a - > argv [ 4 ] ) ;
return CLI_FAILURE ;
}
ast_cli ( a - > fd , " Sorcery memory cache: %s \n " , cache - > name ) ;
ast_cli ( a - > fd , " Number of objects within cache: %d \n " , ao2_container_count ( cache - > objects ) ) ;
if ( cache - > maximum_objects ) {
ast_cli ( a - > fd , " Maximum allowed objects: %d \n " , cache - > maximum_objects ) ;
} else {
ast_cli ( a - > fd , " There is no limit on the maximum number of objects in the cache \n " ) ;
}
if ( cache - > object_lifetime_maximum ) {
ast_cli ( a - > fd , " Number of seconds before object expires: %d \n " , cache - > object_lifetime_maximum ) ;
} else {
ast_cli ( a - > fd , " Object expiration is not enabled - cached objects will not expire \n " ) ;
}
if ( cache - > object_lifetime_stale ) {
ast_cli ( a - > fd , " Number of seconds before object becomes stale: %d \n " , cache - > object_lifetime_stale ) ;
} else {
ast_cli ( a - > fd , " Object staleness is not enabled - cached objects will not go stale \n " ) ;
}
ast_cli ( a - > fd , " Expire all objects on reload: %s \n " , AST_CLI_ONOFF ( cache - > expire_on_reload ) ) ;
ao2_ref ( cache , - 1 ) ;
return CLI_SUCCESS ;
}
/*! \brief Structure used to pass data for printing cached object information */
struct print_object_details {
/*! \brief The sorcery memory cache */
struct sorcery_memory_cache * cache ;
/*! \brief The CLI arguments */
struct ast_cli_args * a ;
} ;
/*!
* \internal
* \brief Callback function for displaying object within the cache
*/
static int sorcery_memory_cache_print_object ( void * obj , void * arg , int flags )
{
# define FORMAT "%-25.25s %-15u %-15u \n"
struct sorcery_memory_cached_object * cached = obj ;
struct print_object_details * details = arg ;
int seconds_until_expire = 0 , seconds_until_stale = 0 ;
if ( details - > cache - > object_lifetime_maximum ) {
seconds_until_expire = ast_tvdiff_ms ( ast_tvadd ( cached - > created , ast_samp2tv ( details - > cache - > object_lifetime_maximum , 1 ) ) , ast_tvnow ( ) ) / 1000 ;
}
if ( details - > cache - > object_lifetime_stale ) {
seconds_until_stale = ast_tvdiff_ms ( ast_tvadd ( cached - > created , ast_samp2tv ( details - > cache - > object_lifetime_stale , 1 ) ) , ast_tvnow ( ) ) / 1000 ;
}
ast_cli ( details - > a - > fd , FORMAT , ast_sorcery_object_get_id ( cached - > object ) , MAX ( seconds_until_stale , 0 ) , MAX ( seconds_until_expire , 0 ) ) ;
return CMP_MATCH ;
# undef FORMAT
}
/*!
* \internal
* \brief CLI command implementation for 'sorcery memory cache dump'
*/
static char * sorcery_memory_cache_dump ( struct ast_cli_entry * e , int cmd , struct ast_cli_args * a )
{
# define FORMAT "%-25.25s %-15.15s %-15.15s \n"
struct sorcery_memory_cache * cache ;
struct print_object_details details ;
switch ( cmd ) {
case CLI_INIT :
e - > command = " sorcery memory cache dump " ;
e - > usage =
" Usage: sorcery memory cache dump <name> \n "
" Dump a list of the objects within the cache, listed by object identifier. \n " ;
return NULL ;
case CLI_GENERATE :
if ( a - > pos = = 4 ) {
return sorcery_memory_cache_complete_name ( a - > word , a - > n ) ;
} else {
return NULL ;
}
}
if ( a - > argc ! = 5 ) {
return CLI_SHOWUSAGE ;
}
cache = ao2_find ( caches , a - > argv [ 4 ] , OBJ_SEARCH_KEY ) ;
if ( ! cache ) {
ast_cli ( a - > fd , " Specified sorcery memory cache '%s' does not exist \n " , a - > argv [ 4 ] ) ;
return CLI_FAILURE ;
}
details . cache = cache ;
details . a = a ;
ast_cli ( a - > fd , " Dumping sorcery memory cache '%s': \n " , cache - > name ) ;
if ( ! cache - > object_lifetime_stale ) {
ast_cli ( a - > fd , " * Staleness is not enabled - objects will not go stale \n " ) ;
}
if ( ! cache - > object_lifetime_maximum ) {
ast_cli ( a - > fd , " * Object lifetime is not enabled - objects will not expire \n " ) ;
}
ast_cli ( a - > fd , FORMAT , " Object Name " , " Stale In " , " Expires In " ) ;
ast_cli ( a - > fd , FORMAT , " ------------------------- " , " --------------- " , " --------------- " ) ;
ao2_callback ( cache - > objects , OBJ_NODATA | OBJ_MULTIPLE , sorcery_memory_cache_print_object , & details ) ;
ast_cli ( a - > fd , FORMAT , " ------------------------- " , " --------------- " , " --------------- " ) ;
ast_cli ( a - > fd , " Total number of objects cached: %d \n " , ao2_container_count ( cache - > objects ) ) ;
ao2_ref ( cache , - 1 ) ;
return CLI_SUCCESS ;
# undef FORMAT
}
/*!
* \internal
* \brief CLI tab completion for cached object names
*/
static char * sorcery_memory_cache_complete_object_name ( const char * cache_name , const char * word , int state )
{
struct sorcery_memory_cache * cache ;
struct sorcery_memory_cached_object * cached ;
struct ao2_iterator it_cached ;
int wordlen = strlen ( word ) ;
int which = 0 ;
char * result = NULL ;
cache = ao2_find ( caches , cache_name , OBJ_SEARCH_KEY ) ;
if ( ! cache ) {
return NULL ;
}
it_cached = ao2_iterator_init ( cache - > objects , 0 ) ;
while ( ( cached = ao2_iterator_next ( & it_cached ) ) ) {
if ( ! strncasecmp ( word , ast_sorcery_object_get_id ( cached - > object ) , wordlen )
& & + + which > state ) {
result = ast_strdup ( ast_sorcery_object_get_id ( cached - > object ) ) ;
}
ao2_ref ( cached , - 1 ) ;
if ( result ) {
break ;
}
}
ao2_iterator_destroy ( & it_cached ) ;
ao2_ref ( cache , - 1 ) ;
return result ;
}
/*!
* \internal
* \brief CLI command implementation for 'sorcery memory cache expire'
*/
static char * sorcery_memory_cache_expire ( struct ast_cli_entry * e , int cmd , struct ast_cli_args * a )
{
struct sorcery_memory_cache * cache ;
switch ( cmd ) {
case CLI_INIT :
e - > command = " sorcery memory cache expire " ;
e - > usage =
" Usage: sorcery memory cache expire <cache name> [object name] \n "
" Expire a specific object or ALL objects within a sorcery memory cache. \n " ;
return NULL ;
case CLI_GENERATE :
if ( a - > pos = = 4 ) {
return sorcery_memory_cache_complete_name ( a - > word , a - > n ) ;
} else if ( a - > pos = = 5 ) {
return sorcery_memory_cache_complete_object_name ( a - > argv [ 4 ] , a - > word , a - > n ) ;
} else {
return NULL ;
}
}
2016-02-25 13:17:04 -07:00
if ( a - > argc < 5 | | a - > argc > 6 ) {
2015-05-26 09:34:47 -03:00
return CLI_SHOWUSAGE ;
}
cache = ao2_find ( caches , a - > argv [ 4 ] , OBJ_SEARCH_KEY ) ;
if ( ! cache ) {
ast_cli ( a - > fd , " Specified sorcery memory cache '%s' does not exist \n " , a - > argv [ 4 ] ) ;
return CLI_FAILURE ;
}
ao2_wrlock ( cache - > objects ) ;
if ( a - > argc = = 5 ) {
remove_all_from_cache ( cache ) ;
ast_cli ( a - > fd , " All objects have been removed from cache '%s' \n " , a - > argv [ 4 ] ) ;
} else {
2020-06-10 06:35:50 -03:00
if ( cache - > full_backend_cache ) {
ast_cli ( a - > fd , " Due to full backend caching per-object expiration is not available on cache '%s' \n " , a - > argv [ 4 ] ) ;
} else if ( ! remove_from_cache ( cache , a - > argv [ 5 ] , 1 ) ) {
2015-05-26 09:34:47 -03:00
ast_cli ( a - > fd , " Successfully expired object '%s' from cache '%s' \n " , a - > argv [ 5 ] , a - > argv [ 4 ] ) ;
} else {
ast_cli ( a - > fd , " Object '%s' was not expired from cache '%s' as it was not found \n " , a - > argv [ 5 ] ,
a - > argv [ 4 ] ) ;
}
}
ao2_unlock ( cache - > objects ) ;
ao2_ref ( cache , - 1 ) ;
return CLI_SUCCESS ;
}
/*!
* \internal
* \brief CLI command implementation for 'sorcery memory cache stale'
*/
static char * sorcery_memory_cache_stale ( struct ast_cli_entry * e , int cmd , struct ast_cli_args * a )
{
struct sorcery_memory_cache * cache ;
2017-02-11 10:57:03 -05:00
int reload = 0 ;
2015-05-26 09:34:47 -03:00
switch ( cmd ) {
case CLI_INIT :
e - > command = " sorcery memory cache stale " ;
e - > usage =
2017-01-25 15:26:53 -06:00
" Usage: sorcery memory cache stale <cache name> [object name [reload]] \n "
" Mark a specific object or ALL objects as stale in a sorcery memory cache. \n "
" If \" reload \" is specified, then the object is marked stale and immediately \n "
" retrieved from backend storage to repopulate the cache \n " ;
2015-05-26 09:34:47 -03:00
return NULL ;
case CLI_GENERATE :
if ( a - > pos = = 4 ) {
return sorcery_memory_cache_complete_name ( a - > word , a - > n ) ;
} else if ( a - > pos = = 5 ) {
return sorcery_memory_cache_complete_object_name ( a - > argv [ 4 ] , a - > word , a - > n ) ;
2017-02-11 10:57:03 -05:00
} else if ( a - > pos = = 6 ) {
static const char * const completions [ ] = { " reload " , NULL } ;
return ast_cli_complete ( a - > word , completions , a - > n ) ;
2015-05-26 09:34:47 -03:00
} else {
return NULL ;
}
}
2017-01-25 15:26:53 -06:00
if ( a - > argc < 5 | | a - > argc > 7 ) {
2015-05-26 09:34:47 -03:00
return CLI_SHOWUSAGE ;
}
2017-02-11 10:57:03 -05:00
if ( a - > argc = = 7 ) {
if ( ! strcasecmp ( a - > argv [ 6 ] , " reload " ) ) {
reload = 1 ;
} else {
return CLI_SHOWUSAGE ;
}
}
2015-05-26 09:34:47 -03:00
cache = ao2_find ( caches , a - > argv [ 4 ] , OBJ_SEARCH_KEY ) ;
if ( ! cache ) {
ast_cli ( a - > fd , " Specified sorcery memory cache '%s' does not exist \n " , a - > argv [ 4 ] ) ;
return CLI_FAILURE ;
}
if ( ! cache - > object_lifetime_stale ) {
ast_cli ( a - > fd , " Specified sorcery memory cache '%s' does not have staleness enabled \n " , a - > argv [ 4 ] ) ;
ao2_ref ( cache , - 1 ) ;
return CLI_FAILURE ;
}
ao2_rdlock ( cache - > objects ) ;
if ( a - > argc = = 5 ) {
mark_all_as_stale_in_cache ( cache ) ;
ast_cli ( a - > fd , " Marked all objects in sorcery memory cache '%s' as stale \n " , a - > argv [ 4 ] ) ;
} else {
if ( ! mark_object_as_stale_in_cache ( cache , a - > argv [ 5 ] ) ) {
ast_cli ( a - > fd , " Successfully marked object '%s' in memory cache '%s' as stale \n " ,
a - > argv [ 5 ] , a - > argv [ 4 ] ) ;
2017-02-11 10:57:03 -05:00
if ( reload ) {
2017-01-25 15:26:53 -06:00
struct sorcery_memory_cached_object * cached ;
cached = ao2_find ( cache - > objects , a - > argv [ 5 ] , OBJ_SEARCH_KEY | OBJ_NOLOCK ) ;
if ( cached ) {
memory_cache_stale_update_object ( cache - > sorcery , cache , cached ) ;
ao2_ref ( cached , - 1 ) ;
}
}
2015-05-26 09:34:47 -03:00
} else {
ast_cli ( a - > fd , " Object '%s' in sorcery memory cache '%s' could not be marked as stale as it was not found \n " ,
a - > argv [ 5 ] , a - > argv [ 4 ] ) ;
}
}
ao2_unlock ( cache - > objects ) ;
ao2_ref ( cache , - 1 ) ;
return CLI_SUCCESS ;
}
2015-12-05 12:01:55 -04:00
/*!
* \internal
* \brief CLI command implementation for 'sorcery memory cache populate'
*/
static char * sorcery_memory_cache_populate ( struct ast_cli_entry * e , int cmd , struct ast_cli_args * a )
{
struct sorcery_memory_cache * cache ;
switch ( cmd ) {
case CLI_INIT :
e - > command = " sorcery memory cache populate " ;
e - > usage =
" Usage: sorcery memory cache populate <cache name> \n "
" Expire all objects in the cache and populate it with ALL objects from backend. \n " ;
return NULL ;
case CLI_GENERATE :
if ( a - > pos = = 4 ) {
return sorcery_memory_cache_complete_name ( a - > word , a - > n ) ;
} else {
return NULL ;
}
}
2016-02-25 13:17:04 -07:00
if ( a - > argc ! = 5 ) {
2015-12-05 12:01:55 -04:00
return CLI_SHOWUSAGE ;
}
cache = ao2_find ( caches , a - > argv [ 4 ] , OBJ_SEARCH_KEY ) ;
if ( ! cache ) {
ast_cli ( a - > fd , " Specified sorcery memory cache '%s' does not exist \n " , a - > argv [ 4 ] ) ;
return CLI_FAILURE ;
}
if ( ! cache - > full_backend_cache ) {
ast_cli ( a - > fd , " Specified sorcery memory cache '%s' does not have full backend caching enabled \n " , a - > argv [ 4 ] ) ;
ao2_ref ( cache , - 1 ) ;
return CLI_FAILURE ;
}
ao2_wrlock ( cache - > objects ) ;
if ( ! cache - > sorcery ) {
ast_cli ( a - > fd , " Specified sorcery memory cache '%s' is no longer active \n " , a - > argv [ 4 ] ) ;
ao2_unlock ( cache - > objects ) ;
ao2_ref ( cache , - 1 ) ;
return CLI_FAILURE ;
}
remove_all_from_cache ( cache ) ;
memory_cache_populate ( cache - > sorcery , cache - > object_type , cache ) ;
ast_cli ( a - > fd , " Specified sorcery memory cache '%s' has been populated with '%d' objects from the backend \n " ,
a - > argv [ 4 ] , ao2_container_count ( cache - > objects ) ) ;
ao2_unlock ( cache - > objects ) ;
ao2_ref ( cache , - 1 ) ;
return CLI_SUCCESS ;
}
2015-05-26 09:34:47 -03:00
static struct ast_cli_entry cli_memory_cache [ ] = {
AST_CLI_DEFINE ( sorcery_memory_cache_show , " Show sorcery memory cache information " ) ,
AST_CLI_DEFINE ( sorcery_memory_cache_dump , " Dump all objects within a sorcery memory cache " ) ,
AST_CLI_DEFINE ( sorcery_memory_cache_expire , " Expire a specific object or ALL objects within a sorcery memory cache " ) ,
AST_CLI_DEFINE ( sorcery_memory_cache_stale , " Mark a specific object or ALL objects as stale within a sorcery memory cache " ) ,
2015-12-05 12:01:55 -04:00
AST_CLI_DEFINE ( sorcery_memory_cache_populate , " Clear and populate the sorcery memory cache with objects from the backend " ) ,
2015-05-26 09:34:47 -03:00
} ;
/*!
* \internal
* \brief AMI command implementation for 'SorceryMemoryCacheExpireObject'
*/
static int sorcery_memory_cache_ami_expire_object ( struct mansession * s , const struct message * m )
{
const char * cache_name = astman_get_header ( m , " Cache " ) ;
const char * object_name = astman_get_header ( m , " Object " ) ;
struct sorcery_memory_cache * cache ;
int res ;
if ( ast_strlen_zero ( cache_name ) ) {
astman_send_error ( s , m , " SorceryMemoryCacheExpireObject requires that a cache name be provided. \n " ) ;
return 0 ;
} else if ( ast_strlen_zero ( object_name ) ) {
astman_send_error ( s , m , " SorceryMemoryCacheExpireObject requires that an object name be provided \n " ) ;
return 0 ;
}
cache = ao2_find ( caches , cache_name , OBJ_SEARCH_KEY ) ;
if ( ! cache ) {
astman_send_error ( s , m , " The provided cache does not exist \n " ) ;
return 0 ;
}
ao2_wrlock ( cache - > objects ) ;
2020-06-10 06:35:50 -03:00
if ( cache - > full_backend_cache ) {
res = 1 ;
} else {
res = remove_from_cache ( cache , object_name , 1 ) ;
}
2015-05-26 09:34:47 -03:00
ao2_unlock ( cache - > objects ) ;
ao2_ref ( cache , - 1 ) ;
2020-06-10 06:35:50 -03:00
if ( res = = 1 ) {
astman_send_error ( s , m , " Due to full backend caching per-object expiration is not available, consider using SorceryMemoryCachePopulate or SorceryMemoryCacheExpire instead \n " ) ;
} else if ( ! res ) {
2015-05-26 09:34:47 -03:00
astman_send_ack ( s , m , " The provided object was expired from the cache \n " ) ;
} else {
astman_send_error ( s , m , " The provided object could not be expired from the cache \n " ) ;
}
return 0 ;
}
/*!
* \internal
* \brief AMI command implementation for 'SorceryMemoryCacheExpire'
*/
static int sorcery_memory_cache_ami_expire ( struct mansession * s , const struct message * m )
{
const char * cache_name = astman_get_header ( m , " Cache " ) ;
struct sorcery_memory_cache * cache ;
if ( ast_strlen_zero ( cache_name ) ) {
astman_send_error ( s , m , " SorceryMemoryCacheExpire requires that a cache name be provided. \n " ) ;
return 0 ;
}
cache = ao2_find ( caches , cache_name , OBJ_SEARCH_KEY ) ;
if ( ! cache ) {
astman_send_error ( s , m , " The provided cache does not exist \n " ) ;
return 0 ;
}
ao2_wrlock ( cache - > objects ) ;
remove_all_from_cache ( cache ) ;
ao2_unlock ( cache - > objects ) ;
ao2_ref ( cache , - 1 ) ;
astman_send_ack ( s , m , " All objects were expired from the cache \n " ) ;
return 0 ;
}
/*!
* \internal
* \brief AMI command implementation for 'SorceryMemoryCacheStaleObject'
*/
static int sorcery_memory_cache_ami_stale_object ( struct mansession * s , const struct message * m )
{
const char * cache_name = astman_get_header ( m , " Cache " ) ;
const char * object_name = astman_get_header ( m , " Object " ) ;
2017-01-25 15:26:53 -06:00
const char * reload = astman_get_header ( m , " Reload " ) ;
2015-05-26 09:34:47 -03:00
struct sorcery_memory_cache * cache ;
int res ;
if ( ast_strlen_zero ( cache_name ) ) {
astman_send_error ( s , m , " SorceryMemoryCacheStaleObject requires that a cache name be provided. \n " ) ;
return 0 ;
} else if ( ast_strlen_zero ( object_name ) ) {
astman_send_error ( s , m , " SorceryMemoryCacheStaleObject requires that an object name be provided \n " ) ;
return 0 ;
}
cache = ao2_find ( caches , cache_name , OBJ_SEARCH_KEY ) ;
if ( ! cache ) {
astman_send_error ( s , m , " The provided cache does not exist \n " ) ;
return 0 ;
}
ao2_rdlock ( cache - > objects ) ;
2017-01-25 15:26:53 -06:00
2015-05-26 09:34:47 -03:00
res = mark_object_as_stale_in_cache ( cache , object_name ) ;
2017-01-25 15:26:53 -06:00
if ( ast_true ( reload ) ) {
struct sorcery_memory_cached_object * cached ;
cached = ao2_find ( cache - > objects , object_name , OBJ_SEARCH_KEY | OBJ_NOLOCK ) ;
if ( cached ) {
memory_cache_stale_update_object ( cache - > sorcery , cache , cached ) ;
ao2_ref ( cached , - 1 ) ;
}
}
2015-05-26 09:34:47 -03:00
ao2_unlock ( cache - > objects ) ;
ao2_ref ( cache , - 1 ) ;
if ( ! res ) {
astman_send_ack ( s , m , " The provided object was marked as stale in the cache \n " ) ;
} else {
astman_send_error ( s , m , " The provided object could not be marked as stale in the cache \n " ) ;
}
return 0 ;
}
/*!
* \internal
* \brief AMI command implementation for 'SorceryMemoryCacheStale'
*/
static int sorcery_memory_cache_ami_stale ( struct mansession * s , const struct message * m )
{
const char * cache_name = astman_get_header ( m , " Cache " ) ;
struct sorcery_memory_cache * cache ;
if ( ast_strlen_zero ( cache_name ) ) {
astman_send_error ( s , m , " SorceryMemoryCacheStale requires that a cache name be provided. \n " ) ;
return 0 ;
}
cache = ao2_find ( caches , cache_name , OBJ_SEARCH_KEY ) ;
if ( ! cache ) {
astman_send_error ( s , m , " The provided cache does not exist \n " ) ;
return 0 ;
}
ao2_rdlock ( cache - > objects ) ;
mark_all_as_stale_in_cache ( cache ) ;
ao2_unlock ( cache - > objects ) ;
ao2_ref ( cache , - 1 ) ;
astman_send_ack ( s , m , " All objects were marked as stale in the cache \n " ) ;
return 0 ;
}
2015-12-05 12:01:55 -04:00
/*!
* \internal
* \brief AMI command implementation for 'SorceryMemoryCachePopulate'
*/
static int sorcery_memory_cache_ami_populate ( struct mansession * s , const struct message * m )
{
const char * cache_name = astman_get_header ( m , " Cache " ) ;
struct sorcery_memory_cache * cache ;
if ( ast_strlen_zero ( cache_name ) ) {
astman_send_error ( s , m , " SorceryMemoryCachePopulate requires that a cache name be provided. \n " ) ;
return 0 ;
}
cache = ao2_find ( caches , cache_name , OBJ_SEARCH_KEY ) ;
if ( ! cache ) {
astman_send_error ( s , m , " The provided cache does not exist \n " ) ;
return 0 ;
}
if ( ! cache - > full_backend_cache ) {
astman_send_error ( s , m , " The provided cache does not have full backend caching enabled \n " ) ;
ao2_ref ( cache , - 1 ) ;
return 0 ;
}
ao2_wrlock ( cache - > objects ) ;
if ( ! cache - > sorcery ) {
astman_send_error ( s , m , " The provided cache is no longer active \n " ) ;
ao2_unlock ( cache - > objects ) ;
ao2_ref ( cache , - 1 ) ;
return 0 ;
}
remove_all_from_cache ( cache ) ;
memory_cache_populate ( cache - > sorcery , cache - > object_type , cache ) ;
ao2_unlock ( cache - > objects ) ;
ao2_ref ( cache , - 1 ) ;
astman_send_ack ( s , m , " Cache has been expired and populated \n " ) ;
return 0 ;
}
2015-05-16 19:02:50 -03:00
# ifdef TEST_FRAMEWORK
/*! \brief Dummy sorcery object */
struct test_sorcery_object {
SORCERY_OBJECT ( details ) ;
} ;
/*!
* \internal
* \brief Allocator for test object
*
* \param id The identifier for the object
*
* \retval non-NULL success
* \retval NULL failure
*/
static void * test_sorcery_object_alloc ( const char * id )
{
return ast_sorcery_generic_alloc ( sizeof ( struct test_sorcery_object ) , NULL ) ;
}
/*!
* \internal
* \brief Allocator for test sorcery instance
*
* \retval non-NULL success
* \retval NULL failure
*/
static struct ast_sorcery * alloc_and_initialize_sorcery ( void )
{
struct ast_sorcery * sorcery ;
if ( ! ( sorcery = ast_sorcery_open ( ) ) ) {
return NULL ;
}
if ( ( ast_sorcery_apply_default ( sorcery , " test " , " memory " , NULL ) ! = AST_SORCERY_APPLY_SUCCESS ) | |
ast_sorcery_internal_object_register ( sorcery , " test " , test_sorcery_object_alloc , NULL , NULL ) ) {
ast_sorcery_unref ( sorcery ) ;
return NULL ;
}
return sorcery ;
}
AST_TEST_DEFINE ( open_with_valid_options )
{
int res = AST_TEST_PASS ;
struct sorcery_memory_cache * cache ;
switch ( cmd ) {
case TEST_INIT :
info - > name = " open_with_valid_options " ;
info - > category = " /res/res_sorcery_memory_cache/ " ;
info - > summary = " Attempt to create sorcery memory caches using valid options " ;
info - > description = " This test performs the following: \n "
" \t * Creates a memory cache with default configuration \n "
" \t * Creates a memory cache with a maximum object count of 10 and verifies it \n "
" \t * Creates a memory cache with a maximum object lifetime of 60 and verifies it \n "
2015-06-24 16:39:38 -05:00
" \t * Creates a memory cache with a stale object lifetime of 90 and verifies it " ;
2015-05-16 19:02:50 -03:00
return AST_TEST_NOT_RUN ;
case TEST_EXECUTE :
break ;
}
cache = sorcery_memory_cache_open ( " " ) ;
if ( ! cache ) {
ast_test_status_update ( test , " Failed to create a sorcery memory cache using default configuration \n " ) ;
res = AST_TEST_FAIL ;
} else {
sorcery_memory_cache_close ( cache ) ;
}
cache = sorcery_memory_cache_open ( " maximum_objects=10 " ) ;
if ( ! cache ) {
ast_test_status_update ( test , " Failed to create a sorcery memory cache with a maximum object count of 10 \n " ) ;
res = AST_TEST_FAIL ;
} else {
if ( cache - > maximum_objects ! = 10 ) {
ast_test_status_update ( test , " Created a sorcery memory cache with a maximum object count of 10 but it has '%u' \n " ,
cache - > maximum_objects ) ;
}
sorcery_memory_cache_close ( cache ) ;
}
cache = sorcery_memory_cache_open ( " object_lifetime_maximum=60 " ) ;
if ( ! cache ) {
ast_test_status_update ( test , " Failed to create a sorcery memory cache with a maximum object lifetime of 60 \n " ) ;
res = AST_TEST_FAIL ;
} else {
if ( cache - > object_lifetime_maximum ! = 60 ) {
ast_test_status_update ( test , " Created a sorcery memory cache with a maximum object lifetime of 60 but it has '%u' \n " ,
cache - > object_lifetime_maximum ) ;
}
sorcery_memory_cache_close ( cache ) ;
}
cache = sorcery_memory_cache_open ( " object_lifetime_stale=90 " ) ;
if ( ! cache ) {
ast_test_status_update ( test , " Failed to create a sorcery memory cache with a stale object lifetime of 90 \n " ) ;
res = AST_TEST_FAIL ;
} else {
if ( cache - > object_lifetime_stale ! = 90 ) {
ast_test_status_update ( test , " Created a sorcery memory cache with a stale object lifetime of 90 but it has '%u' \n " ,
cache - > object_lifetime_stale ) ;
}
sorcery_memory_cache_close ( cache ) ;
}
return res ;
}
AST_TEST_DEFINE ( open_with_invalid_options )
{
int res = AST_TEST_PASS ;
struct sorcery_memory_cache * cache ;
switch ( cmd ) {
case TEST_INIT :
info - > name = " open_with_invalid_options " ;
info - > category = " /res/res_sorcery_memory_cache/ " ;
info - > summary = " Attempt to create sorcery memory caches using invalid options " ;
info - > description = " This test attempts to perform the following: \n "
" \t * Create a memory cache with an empty name \n "
" \t * Create a memory cache with a maximum object count of -1 \n "
" \t * Create a memory cache with a maximum object count of toast \n "
" \t * Create a memory cache with a maximum object lifetime of -1 \n "
" \t * Create a memory cache with a maximum object lifetime of toast \n "
" \t * Create a memory cache with a stale object lifetime of -1 \n "
2015-06-24 16:39:38 -05:00
" \t * Create a memory cache with a stale object lifetime of toast " ;
2015-05-16 19:02:50 -03:00
return AST_TEST_NOT_RUN ;
case TEST_EXECUTE :
break ;
}
cache = sorcery_memory_cache_open ( " name= " ) ;
if ( cache ) {
ast_test_status_update ( test , " Created a sorcery memory cache with an empty name \n " ) ;
sorcery_memory_cache_close ( cache ) ;
res = AST_TEST_FAIL ;
}
cache = sorcery_memory_cache_open ( " maximum_objects=-1 " ) ;
if ( cache ) {
ast_test_status_update ( test , " Created a sorcery memory cache with a maximum object count of -1 \n " ) ;
sorcery_memory_cache_close ( cache ) ;
res = AST_TEST_FAIL ;
}
cache = sorcery_memory_cache_open ( " maximum_objects=toast " ) ;
if ( cache ) {
ast_test_status_update ( test , " Created a sorcery memory cache with a maximum object count of toast \n " ) ;
sorcery_memory_cache_close ( cache ) ;
res = AST_TEST_FAIL ;
}
cache = sorcery_memory_cache_open ( " object_lifetime_maximum=-1 " ) ;
if ( cache ) {
ast_test_status_update ( test , " Created a sorcery memory cache with an object lifetime maximum of -1 \n " ) ;
sorcery_memory_cache_close ( cache ) ;
res = AST_TEST_FAIL ;
}
cache = sorcery_memory_cache_open ( " object_lifetime_maximum=toast " ) ;
if ( cache ) {
ast_test_status_update ( test , " Created a sorcery memory cache with an object lifetime maximum of toast \n " ) ;
sorcery_memory_cache_close ( cache ) ;
res = AST_TEST_FAIL ;
}
cache = sorcery_memory_cache_open ( " object_lifetime_stale=-1 " ) ;
if ( cache ) {
ast_test_status_update ( test , " Created a sorcery memory cache with a stale object lifetime of -1 \n " ) ;
sorcery_memory_cache_close ( cache ) ;
res = AST_TEST_FAIL ;
}
cache = sorcery_memory_cache_open ( " object_lifetime_stale=toast " ) ;
if ( cache ) {
ast_test_status_update ( test , " Created a sorcery memory cache with a stale object lifetime of toast \n " ) ;
sorcery_memory_cache_close ( cache ) ;
res = AST_TEST_FAIL ;
}
cache = sorcery_memory_cache_open ( " tacos " ) ;
if ( cache ) {
ast_test_status_update ( test , " Created a sorcery memory cache with an invalid configuration option 'tacos' \n " ) ;
sorcery_memory_cache_close ( cache ) ;
res = AST_TEST_FAIL ;
}
return res ;
}
AST_TEST_DEFINE ( create_and_retrieve )
{
int res = AST_TEST_FAIL ;
struct ast_sorcery * sorcery = NULL ;
struct sorcery_memory_cache * cache = NULL ;
RAII_VAR ( void * , object , NULL , ao2_cleanup ) ;
RAII_VAR ( void * , cached_object , NULL , ao2_cleanup ) ;
switch ( cmd ) {
case TEST_INIT :
info - > name = " create " ;
info - > category = " /res/res_sorcery_memory_cache/ " ;
info - > summary = " Attempt to create an object in the cache " ;
info - > description = " This test performs the following: \n "
" \t * Creates a memory cache with default options \n "
" \t * Creates a sorcery instance with a test object \n "
" \t * Creates a test object with an id of test \n "
" \t * Pushes the test object into the memory cache \n "
2015-06-24 16:39:38 -05:00
" \t * Confirms that the test object is in the cache " ;
2015-05-16 19:02:50 -03:00
return AST_TEST_NOT_RUN ;
case TEST_EXECUTE :
break ;
}
cache = sorcery_memory_cache_open ( " " ) ;
if ( ! cache ) {
ast_test_status_update ( test , " Failed to create a sorcery memory cache using default options \n " ) ;
goto cleanup ;
}
if ( ao2_container_count ( cache - > objects ) ) {
ast_test_status_update ( test , " Memory cache contains cached objects before we added one \n " ) ;
goto cleanup ;
}
sorcery = alloc_and_initialize_sorcery ( ) ;
if ( ! sorcery ) {
ast_test_status_update ( test , " Failed to create a test sorcery instance \n " ) ;
goto cleanup ;
}
object = ast_sorcery_alloc ( sorcery , " test " , " test " ) ;
if ( ! object ) {
ast_test_status_update ( test , " Failed to allocate a test object \n " ) ;
goto cleanup ;
}
sorcery_memory_cache_create ( sorcery , cache , object ) ;
if ( ! ao2_container_count ( cache - > objects ) ) {
ast_test_status_update ( test , " Added test object to memory cache but cache remains empty \n " ) ;
goto cleanup ;
}
cached_object = sorcery_memory_cache_retrieve_id ( sorcery , cache , " test " , " test " ) ;
if ( ! cached_object ) {
ast_test_status_update ( test , " Object placed into memory cache could not be retrieved \n " ) ;
goto cleanup ;
}
if ( cached_object ! = object ) {
ast_test_status_update ( test , " Object retrieved from memory cached is not the one we cached \n " ) ;
goto cleanup ;
}
res = AST_TEST_PASS ;
cleanup :
if ( cache ) {
sorcery_memory_cache_close ( cache ) ;
}
if ( sorcery ) {
ast_sorcery_unref ( sorcery ) ;
}
return res ;
}
AST_TEST_DEFINE ( update )
{
int res = AST_TEST_FAIL ;
struct ast_sorcery * sorcery = NULL ;
struct sorcery_memory_cache * cache = NULL ;
RAII_VAR ( void * , original_object , NULL , ao2_cleanup ) ;
RAII_VAR ( void * , updated_object , NULL , ao2_cleanup ) ;
RAII_VAR ( void * , cached_object , NULL , ao2_cleanup ) ;
switch ( cmd ) {
case TEST_INIT :
info - > name = " create " ;
info - > category = " /res/res_sorcery_memory_cache/ " ;
info - > summary = " Attempt to create and then update an object in the cache " ;
info - > description = " This test performs the following: \n "
" \t * Creates a memory cache with default options \n "
" \t * Creates a sorcery instance with a test object \n "
" \t * Creates a test object with an id of test \n "
" \t * Pushes the test object into the memory cache \n "
" \t * Confirms that the test object is in the cache \n "
" \t * Creates a new test object with the same id of test \n "
" \t * Pushes the new test object into the memory cache \n "
2015-06-24 16:39:38 -05:00
" \t * Confirms that the new test object has replaced the old one " ;
2015-05-16 19:02:50 -03:00
return AST_TEST_NOT_RUN ;
case TEST_EXECUTE :
break ;
}
cache = sorcery_memory_cache_open ( " " ) ;
if ( ! cache ) {
ast_test_status_update ( test , " Failed to create a sorcery memory cache using default options \n " ) ;
goto cleanup ;
}
if ( ao2_container_count ( cache - > objects ) ) {
ast_test_status_update ( test , " Memory cache contains cached objects before we added one \n " ) ;
goto cleanup ;
}
sorcery = alloc_and_initialize_sorcery ( ) ;
if ( ! sorcery ) {
ast_test_status_update ( test , " Failed to create a test sorcery instance \n " ) ;
goto cleanup ;
}
original_object = ast_sorcery_alloc ( sorcery , " test " , " test " ) ;
if ( ! original_object ) {
ast_test_status_update ( test , " Failed to allocate a test object \n " ) ;
goto cleanup ;
}
sorcery_memory_cache_create ( sorcery , cache , original_object ) ;
updated_object = ast_sorcery_alloc ( sorcery , " test " , " test " ) ;
if ( ! updated_object ) {
ast_test_status_update ( test , " Failed to allocate an updated test object \n " ) ;
goto cleanup ;
}
sorcery_memory_cache_create ( sorcery , cache , updated_object ) ;
if ( ao2_container_count ( cache - > objects ) ! = 1 ) {
ast_test_status_update ( test , " Added updated test object to memory cache but cache now contains %d objects instead of 1 \n " ,
ao2_container_count ( cache - > objects ) ) ;
goto cleanup ;
}
cached_object = sorcery_memory_cache_retrieve_id ( sorcery , cache , " test " , " test " ) ;
if ( ! cached_object ) {
ast_test_status_update ( test , " Updated object placed into memory cache could not be retrieved \n " ) ;
goto cleanup ;
}
if ( cached_object = = original_object ) {
ast_test_status_update ( test , " Updated object placed into memory cache but old one is being retrieved \n " ) ;
goto cleanup ;
} else if ( cached_object ! = updated_object ) {
ast_test_status_update ( test , " Updated object placed into memory cache but different one is being retrieved \n " ) ;
goto cleanup ;
}
res = AST_TEST_PASS ;
cleanup :
if ( cache ) {
sorcery_memory_cache_close ( cache ) ;
}
if ( sorcery ) {
ast_sorcery_unref ( sorcery ) ;
}
return res ;
}
AST_TEST_DEFINE ( delete )
{
int res = AST_TEST_FAIL ;
struct ast_sorcery * sorcery = NULL ;
struct sorcery_memory_cache * cache = NULL ;
RAII_VAR ( void * , object , NULL , ao2_cleanup ) ;
RAII_VAR ( void * , cached_object , NULL , ao2_cleanup ) ;
switch ( cmd ) {
case TEST_INIT :
info - > name = " delete " ;
info - > category = " /res/res_sorcery_memory_cache/ " ;
info - > summary = " Attempt to create and then delete an object in the cache " ;
info - > description = " This test performs the following: \n "
" \t * Creates a memory cache with default options \n "
" \t * Creates a sorcery instance with a test object \n "
" \t * Creates a test object with an id of test \n "
" \t * Pushes the test object into the memory cache \n "
" \t * Confirms that the test object is in the cache \n "
" \t * Deletes the test object from the cache \n "
2015-06-24 16:39:38 -05:00
" \t * Confirms that the test object is no longer in the cache " ;
2015-05-16 19:02:50 -03:00
return AST_TEST_NOT_RUN ;
case TEST_EXECUTE :
break ;
}
cache = sorcery_memory_cache_open ( " " ) ;
if ( ! cache ) {
ast_test_status_update ( test , " Failed to create a sorcery memory cache using default options \n " ) ;
goto cleanup ;
}
if ( ao2_container_count ( cache - > objects ) ) {
ast_test_status_update ( test , " Memory cache contains cached objects before we added one \n " ) ;
goto cleanup ;
}
sorcery = alloc_and_initialize_sorcery ( ) ;
if ( ! sorcery ) {
ast_test_status_update ( test , " Failed to create a test sorcery instance \n " ) ;
goto cleanup ;
}
object = ast_sorcery_alloc ( sorcery , " test " , " test " ) ;
if ( ! object ) {
ast_test_status_update ( test , " Failed to allocate a test object \n " ) ;
goto cleanup ;
}
sorcery_memory_cache_create ( sorcery , cache , object ) ;
if ( ! ao2_container_count ( cache - > objects ) ) {
ast_test_status_update ( test , " Added test object to memory cache but cache contains no objects \n " ) ;
goto cleanup ;
}
cached_object = sorcery_memory_cache_retrieve_id ( sorcery , cache , " test " , " test " ) ;
if ( ! cached_object ) {
ast_test_status_update ( test , " Test object placed into memory cache could not be retrieved \n " ) ;
goto cleanup ;
}
ao2_ref ( cached_object , - 1 ) ;
cached_object = NULL ;
sorcery_memory_cache_delete ( sorcery , cache , object ) ;
cached_object = sorcery_memory_cache_retrieve_id ( sorcery , cache , " test " , " test " ) ;
if ( cached_object ) {
ast_test_status_update ( test , " Test object deleted from memory cache can still be retrieved \n " ) ;
goto cleanup ;
}
res = AST_TEST_PASS ;
cleanup :
if ( cache ) {
sorcery_memory_cache_close ( cache ) ;
}
if ( sorcery ) {
ast_sorcery_unref ( sorcery ) ;
}
return res ;
}
2015-05-20 15:19:27 -05:00
static int check_cache_content ( struct ast_test * test , struct ast_sorcery * sorcery , struct sorcery_memory_cache * cache ,
const char * * in_cache , size_t num_in_cache , const char * * not_in_cache , size_t num_not_in_cache )
{
int i ;
int res = 0 ;
RAII_VAR ( void * , cached_object , NULL , ao2_cleanup ) ;
for ( i = 0 ; i < num_in_cache ; + + i ) {
cached_object = sorcery_memory_cache_retrieve_id ( sorcery , cache , " test " , in_cache [ i ] ) ;
if ( ! cached_object ) {
ast_test_status_update ( test , " Failed to retrieve '%s' object from the cache \n " ,
in_cache [ i ] ) ;
res = - 1 ;
}
ao2_ref ( cached_object , - 1 ) ;
}
for ( i = 0 ; i < num_not_in_cache ; + + i ) {
cached_object = sorcery_memory_cache_retrieve_id ( sorcery , cache , " test " , not_in_cache [ i ] ) ;
if ( cached_object ) {
ast_test_status_update ( test , " Retrieved '%s' object from the cache unexpectedly \n " ,
not_in_cache [ i ] ) ;
ao2_ref ( cached_object , - 1 ) ;
res = - 1 ;
}
}
return res ;
}
AST_TEST_DEFINE ( maximum_objects )
{
int res = AST_TEST_FAIL ;
struct ast_sorcery * sorcery = NULL ;
struct sorcery_memory_cache * cache = NULL ;
RAII_VAR ( void * , alice , NULL , ao2_cleanup ) ;
RAII_VAR ( void * , bob , NULL , ao2_cleanup ) ;
RAII_VAR ( void * , charlie , NULL , ao2_cleanup ) ;
RAII_VAR ( void * , cached_object , NULL , ao2_cleanup ) ;
const char * in_cache [ 2 ] ;
const char * not_in_cache [ 2 ] ;
switch ( cmd ) {
case TEST_INIT :
info - > name = " maximum_objects " ;
info - > category = " /res/res_sorcery_memory_cache/ " ;
info - > summary = " Ensure that the 'maximum_objects' option works as expected " ;
info - > description = " This test performs the following: \n "
" \t * Creates a memory cache with maximum_objects=2 \n "
" \t * Creates a sorcery instance \n "
" \t * Creates a three test objects: alice, bob, charlie, and david \n "
" \t * Pushes alice and bob into the memory cache \n "
" \t * Confirms that alice and bob are in the memory cache \n "
" \t * Pushes charlie into the memory cache \n "
" \t * Confirms that bob and charlie are in the memory cache \n "
" \t * Deletes charlie from the memory cache \n "
" \t * Confirms that only bob is in the memory cache \n "
" \t * Pushes alice into the memory cache \n "
2015-06-24 16:39:38 -05:00
" \t * Confirms that bob and alice are in the memory cache " ;
2015-05-20 15:19:27 -05:00
return AST_TEST_NOT_RUN ;
case TEST_EXECUTE :
break ;
}
cache = sorcery_memory_cache_open ( " maximum_objects=2 " ) ;
if ( ! cache ) {
ast_test_status_update ( test , " Failed to create a sorcery memory cache with maximum_objects=2 \n " ) ;
goto cleanup ;
}
if ( ao2_container_count ( cache - > objects ) ) {
ast_test_status_update ( test , " Memory cache contains cached objects before we added one \n " ) ;
goto cleanup ;
}
sorcery = alloc_and_initialize_sorcery ( ) ;
if ( ! sorcery ) {
ast_test_status_update ( test , " Failed to create a test sorcery instance \n " ) ;
goto cleanup ;
}
alice = ast_sorcery_alloc ( sorcery , " test " , " alice " ) ;
bob = ast_sorcery_alloc ( sorcery , " test " , " bob " ) ;
charlie = ast_sorcery_alloc ( sorcery , " test " , " charlie " ) ;
if ( ! alice | | ! bob | | ! charlie ) {
ast_test_status_update ( test , " Failed to allocate sorcery object(s) \n " ) ;
goto cleanup ;
}
sorcery_memory_cache_create ( sorcery , cache , alice ) ;
in_cache [ 0 ] = " alice " ;
in_cache [ 1 ] = NULL ;
not_in_cache [ 0 ] = " bob " ;
not_in_cache [ 1 ] = " charlie " ;
if ( check_cache_content ( test , sorcery , cache , in_cache , 1 , not_in_cache , 2 ) ) {
goto cleanup ;
}
/* Delays are added to ensure that we are not adding cache entries within the
* same microsecond
*/
usleep ( 1000 ) ;
sorcery_memory_cache_create ( sorcery , cache , bob ) ;
in_cache [ 0 ] = " alice " ;
in_cache [ 1 ] = " bob " ;
not_in_cache [ 0 ] = " charlie " ;
not_in_cache [ 1 ] = NULL ;
if ( check_cache_content ( test , sorcery , cache , in_cache , 2 , not_in_cache , 1 ) ) {
goto cleanup ;
}
usleep ( 1000 ) ;
sorcery_memory_cache_create ( sorcery , cache , charlie ) ;
in_cache [ 0 ] = " bob " ;
in_cache [ 1 ] = " charlie " ;
not_in_cache [ 0 ] = " alice " ;
not_in_cache [ 1 ] = NULL ;
if ( check_cache_content ( test , sorcery , cache , in_cache , 2 , not_in_cache , 1 ) ) {
goto cleanup ;
}
usleep ( 1000 ) ;
sorcery_memory_cache_delete ( sorcery , cache , charlie ) ;
in_cache [ 0 ] = " bob " ;
in_cache [ 1 ] = NULL ;
not_in_cache [ 0 ] = " alice " ;
not_in_cache [ 1 ] = " charlie " ;
if ( check_cache_content ( test , sorcery , cache , in_cache , 1 , not_in_cache , 2 ) ) {
goto cleanup ;
}
usleep ( 1000 ) ;
sorcery_memory_cache_create ( sorcery , cache , alice ) ;
in_cache [ 0 ] = " bob " ;
in_cache [ 1 ] = " alice " ;
not_in_cache [ 0 ] = " charlie " ;
not_in_cache [ 1 ] = NULL ;
if ( check_cache_content ( test , sorcery , cache , in_cache , 2 , not_in_cache , 1 ) ) {
goto cleanup ;
}
res = AST_TEST_PASS ;
cleanup :
if ( cache ) {
sorcery_memory_cache_close ( cache ) ;
}
if ( sorcery ) {
ast_sorcery_unref ( sorcery ) ;
}
return res ;
}
2015-05-20 19:35:54 -03:00
AST_TEST_DEFINE ( expiration )
{
int res = AST_TEST_FAIL ;
struct ast_sorcery * sorcery = NULL ;
struct sorcery_memory_cache * cache = NULL ;
int i ;
switch ( cmd ) {
case TEST_INIT :
info - > name = " expiration " ;
info - > category = " /res/res_sorcery_memory_cache/ " ;
info - > summary = " Add objects to a cache configured with maximum lifetime, confirm they are removed " ;
info - > description = " This test performs the following: \n "
" \t * Creates a memory cache with a maximum object lifetime of 5 seconds \n "
" \t * Pushes 10 objects into the memory cache \n "
" \t * Waits (up to) 10 seconds for expiration to occur \n "
2015-06-24 16:39:38 -05:00
" \t * Confirms that the objects have been removed from the cache " ;
2015-05-20 19:35:54 -03:00
return AST_TEST_NOT_RUN ;
case TEST_EXECUTE :
break ;
}
cache = sorcery_memory_cache_open ( " object_lifetime_maximum=5 " ) ;
if ( ! cache ) {
ast_test_status_update ( test , " Failed to create a sorcery memory cache using default options \n " ) ;
goto cleanup ;
}
sorcery = alloc_and_initialize_sorcery ( ) ;
if ( ! sorcery ) {
ast_test_status_update ( test , " Failed to create a test sorcery instance \n " ) ;
goto cleanup ;
}
cache - > cache_notify = 1 ;
ast_mutex_init ( & cache - > lock ) ;
ast_cond_init ( & cache - > cond , NULL ) ;
for ( i = 0 ; i < 5 ; + + i ) {
char uuid [ AST_UUID_STR_LEN ] ;
void * object ;
object = ast_sorcery_alloc ( sorcery , " test " , ast_uuid_generate_str ( uuid , sizeof ( uuid ) ) ) ;
if ( ! object ) {
ast_test_status_update ( test , " Failed to allocate test object for expiration \n " ) ;
goto cleanup ;
}
sorcery_memory_cache_create ( sorcery , cache , object ) ;
ao2_ref ( object , - 1 ) ;
}
ast_mutex_lock ( & cache - > lock ) ;
while ( ! cache - > cache_completed ) {
struct timeval start = ast_tvnow ( ) ;
struct timespec end = {
. tv_sec = start . tv_sec + 10 ,
. tv_nsec = start . tv_usec * 1000 ,
} ;
if ( ast_cond_timedwait ( & cache - > cond , & cache - > lock , & end ) = = ETIMEDOUT ) {
break ;
}
}
ast_mutex_unlock ( & cache - > lock ) ;
if ( ao2_container_count ( cache - > objects ) ) {
ast_test_status_update ( test , " Objects placed into the memory cache did not expire and get removed \n " ) ;
goto cleanup ;
}
res = AST_TEST_PASS ;
cleanup :
if ( cache ) {
if ( cache - > cache_notify ) {
ast_cond_destroy ( & cache - > cond ) ;
ast_mutex_destroy ( & cache - > lock ) ;
}
sorcery_memory_cache_close ( cache ) ;
}
if ( sorcery ) {
ast_sorcery_unref ( sorcery ) ;
}
return res ;
}
2015-05-26 13:01:24 -05:00
/*!
* \brief Backend data that the mock sorcery wizard uses to create objects
*/
static struct backend_data {
/*! An arbitrary data field */
int salt ;
/*! Another arbitrary data field */
int pepper ;
/*! Indicates whether the backend has data */
int exists ;
} * real_backend_data ;
/*!
* \brief Sorcery object created based on backend data
*/
struct test_data {
SORCERY_OBJECT ( details ) ;
/*! Mirrors the backend data's salt field */
int salt ;
/*! Mirrors the backend data's pepper field */
int pepper ;
} ;
/*!
* \brief Allocation callback for test_data sorcery object
*/
static void * test_data_alloc ( const char * id ) {
return ast_sorcery_generic_alloc ( sizeof ( struct test_data ) , NULL ) ;
}
/*!
* \brief Callback for retrieving sorcery object by ID
*
* The mock wizard uses the \ref real_backend_data in order to construct
* objects. If the backend data is "nonexisent" then no object is returned.
* Otherwise, an object is created that has the backend data's salt and
* pepper values copied.
*
* \param sorcery The sorcery instance
* \param data Unused
* \param type The object type. Will always be "test".
* \param id The object id. Will always be "test".
*
* \retval NULL Backend data does not exist
* \retval non-NULL An object representing the backend data
*/
static void * mock_retrieve_id ( const struct ast_sorcery * sorcery , void * data ,
const char * type , const char * id )
{
struct test_data * b_data ;
if ( ! real_backend_data - > exists ) {
return NULL ;
}
b_data = ast_sorcery_alloc ( sorcery , type , id ) ;
if ( ! b_data ) {
return NULL ;
}
b_data - > salt = real_backend_data - > salt ;
b_data - > pepper = real_backend_data - > pepper ;
return b_data ;
}
2015-12-05 12:01:55 -04:00
/*!
* \brief Callback for retrieving multiple sorcery objects
*
* The mock wizard uses the \ref real_backend_data in order to construct
* objects. If the backend data is "nonexisent" then no object is returned.
* Otherwise, the number of objects matching the exists value will be returned.
*
* \param sorcery The sorcery instance
* \param data Unused
* \param type The object type. Will always be "test".
* \param objects Container to place objects into.
* \param fields Fields to search for.
*/
static void mock_retrieve_multiple ( const struct ast_sorcery * sorcery , void * data ,
const char * type , struct ao2_container * objects , const struct ast_variable * fields )
{
int i ;
if ( fields ) {
return ;
}
for ( i = 0 ; i < real_backend_data - > exists ; + + i ) {
char uuid [ AST_UUID_STR_LEN ] ;
struct test_data * b_data ;
b_data = ast_sorcery_alloc ( sorcery , type , ast_uuid_generate_str ( uuid , sizeof ( uuid ) ) ) ;
if ( ! b_data ) {
continue ;
}
b_data - > salt = real_backend_data - > salt ;
b_data - > pepper = real_backend_data - > pepper ;
ao2_link ( objects , b_data ) ;
ao2_ref ( b_data , - 1 ) ;
}
}
2015-05-26 13:01:24 -05:00
/*!
* \brief A mock sorcery wizard used for the stale test
*/
static struct ast_sorcery_wizard mock_wizard = {
. name = " mock " ,
. retrieve_id = mock_retrieve_id ,
2015-12-05 12:01:55 -04:00
. retrieve_multiple = mock_retrieve_multiple ,
2015-05-26 13:01:24 -05:00
} ;
/*!
* \brief Wait for the cache to be updated after a stale object is retrieved.
*
* Since the cache does not know what type of objects it is dealing with, and
* since we do not have the internals of the cache, the only way to make this
* determination is to continuously retrieve an object from the cache until
* we retrieve a different object than we had previously retrieved.
*
* \param sorcery The sorcery instance
* \param previous_object The object we had previously retrieved from the cache
* \param[out] new_object The new object we retrieve from the cache
*
* \retval 0 Successfully retrieved a new object from the cache
* \retval non-zero Failed to retrieve a new object from the cache
*/
static int wait_for_cache_update ( const struct ast_sorcery * sorcery ,
void * previous_object , struct test_data * * new_object )
{
struct timeval start = ast_tvnow ( ) ;
while ( ast_remaining_ms ( start , 5000 ) > 0 ) {
void * object ;
object = ast_sorcery_retrieve_by_id ( sorcery , " test " , " test " ) ;
if ( object ! = previous_object ) {
* new_object = object ;
return 0 ;
}
ao2_cleanup ( object ) ;
}
return - 1 ;
}
AST_TEST_DEFINE ( stale )
{
int res = AST_TEST_FAIL ;
struct ast_sorcery * sorcery = NULL ;
struct test_data * backend_object ;
struct backend_data iterations [ ] = {
{ . salt = 1 , . pepper = 2 , . exists = 1 } ,
{ . salt = 568729 , . pepper = - 234123 , . exists = 1 } ,
{ . salt = 0 , . pepper = 0 , . exists = 0 } ,
} ;
struct backend_data initial = {
. salt = 0 ,
. pepper = 0 ,
. exists = 1 ,
} ;
int i ;
switch ( cmd ) {
case TEST_INIT :
info - > name = " stale " ;
info - > category = " /res/res_sorcery_memory_cache/ " ;
info - > summary = " Ensure that stale objects are replaced with updated objects " ;
info - > description = " This test performs the following: \n "
" \t * Create a sorcery instance with two wizards "
" \t \t * The first is a memory cache that marks items stale after 3 seconds \n "
" \t \t * The second is a mock of a back-end \n "
" \t * Pre-populates the cache by retrieving some initial data from the backend. \n "
" \t * Performs iterations of the following: \n "
" \t \t * Update backend data with new values \n "
" \t \t * Retrieve item from the cache \n "
" \t \t * Ensure the retrieved item does not have the new backend values \n "
" \t \t * Wait for cached object to become stale \n "
" \t \t * Retrieve the stale cached object \n "
" \t \t * Ensure that the stale object retrieved is the same as the fresh one from earlier \n "
" \t \t * Wait for the cache to update with new data \n "
2015-06-24 16:39:38 -05:00
" \t \t * Ensure that new data in the cache matches backend data " ;
2015-05-26 13:01:24 -05:00
return AST_TEST_NOT_RUN ;
case TEST_EXECUTE :
break ;
}
ast_sorcery_wizard_register ( & mock_wizard ) ;
sorcery = ast_sorcery_open ( ) ;
if ( ! sorcery ) {
ast_test_status_update ( test , " Failed to create sorcery instance \n " ) ;
goto cleanup ;
}
ast_sorcery_apply_wizard_mapping ( sorcery , " test " , " memory_cache " ,
" object_lifetime_stale=3 " , 1 ) ;
ast_sorcery_apply_wizard_mapping ( sorcery , " test " , " mock " , NULL , 0 ) ;
ast_sorcery_internal_object_register ( sorcery , " test " , test_data_alloc , NULL , NULL ) ;
/* Prepopulate the cache */
real_backend_data = & initial ;
backend_object = ast_sorcery_retrieve_by_id ( sorcery , " test " , " test " ) ;
if ( ! backend_object ) {
ast_test_status_update ( test , " Unable to retrieve backend data and populate the cache \n " ) ;
goto cleanup ;
}
ao2_ref ( backend_object , - 1 ) ;
for ( i = 0 ; i < ARRAY_LEN ( iterations ) ; + + i ) {
RAII_VAR ( struct test_data * , cache_fresh , NULL , ao2_cleanup ) ;
RAII_VAR ( struct test_data * , cache_stale , NULL , ao2_cleanup ) ;
RAII_VAR ( struct test_data * , cache_new , NULL , ao2_cleanup ) ;
real_backend_data = & iterations [ i ] ;
ast_test_status_update ( test , " Begininning iteration %d \n " , i ) ;
cache_fresh = ast_sorcery_retrieve_by_id ( sorcery , " test " , " test " ) ;
if ( ! cache_fresh ) {
ast_test_status_update ( test , " Unable to retrieve fresh cached object \n " ) ;
goto cleanup ;
}
if ( cache_fresh - > salt = = iterations [ i ] . salt | | cache_fresh - > pepper = = iterations [ i ] . pepper ) {
ast_test_status_update ( test , " Fresh cached object has unexpected values. Did we hit the backend? \n " ) ;
goto cleanup ;
}
sleep ( 5 ) ;
cache_stale = ast_sorcery_retrieve_by_id ( sorcery , " test " , " test " ) ;
if ( ! cache_stale ) {
ast_test_status_update ( test , " Unable to retrieve stale cached object \n " ) ;
goto cleanup ;
}
if ( cache_stale ! = cache_fresh ) {
ast_test_status_update ( test , " Stale cache hit retrieved different object than fresh cache hit \n " ) ;
goto cleanup ;
}
if ( wait_for_cache_update ( sorcery , cache_stale , & cache_new ) ) {
ast_test_status_update ( test , " Cache was not updated \n " ) ;
goto cleanup ;
}
if ( iterations [ i ] . exists ) {
if ( ! cache_new ) {
ast_test_status_update ( test , " Failed to retrieve item from cache when there should be one present \n " ) ;
goto cleanup ;
} else if ( cache_new - > salt ! = iterations [ i ] . salt | |
cache_new - > pepper ! = iterations [ i ] . pepper ) {
ast_test_status_update ( test , " New cached item has unexpected values \n " ) ;
goto cleanup ;
}
} else if ( cache_new ) {
ast_test_status_update ( test , " Retrieved a cached item when there should not have been one present \n " ) ;
goto cleanup ;
}
}
res = AST_TEST_PASS ;
cleanup :
if ( sorcery ) {
ast_sorcery_unref ( sorcery ) ;
}
ast_sorcery_wizard_unregister ( & mock_wizard ) ;
return res ;
}
2015-12-05 12:01:55 -04:00
AST_TEST_DEFINE ( full_backend_cache_expiration )
{
int res = AST_TEST_FAIL ;
struct ast_sorcery * sorcery = NULL ;
struct backend_data initial = {
. salt = 0 ,
. pepper = 0 ,
. exists = 4 ,
} ;
struct ao2_container * objects ;
ast_mutex_t lock ;
ast_cond_t cond ;
struct timeval start ;
struct timespec end ;
switch ( cmd ) {
case TEST_INIT :
info - > name = " full_backend_cache_expiration " ;
info - > category = " /res/res_sorcery_memory_cache/ " ;
info - > summary = " Ensure that the full backend cache actually caches the backend " ;
info - > description = " This test performs the following: \n "
" \t * Create a sorcery instance with two wizards "
" \t \t * The first is a memory cache that expires objects after 3 seconds and does full backend caching \n "
" \t \t * The second is a mock of a back-end \n "
" \t * Populates the cache by requesting all objects which returns 4. \n "
" \t * Updates the backend to contain a different number of objects, 8. \n "
" \t * Requests all objects and confirms the number returned is only 4. \n "
" \t * Wait for cached objects to expire. \n "
" \t * Requests all objects and confirms the number returned is 8. " ;
return AST_TEST_NOT_RUN ;
case TEST_EXECUTE :
break ;
}
ast_sorcery_wizard_register ( & mock_wizard ) ;
sorcery = ast_sorcery_open ( ) ;
if ( ! sorcery ) {
ast_test_status_update ( test , " Failed to create sorcery instance \n " ) ;
goto cleanup ;
}
ast_sorcery_apply_wizard_mapping ( sorcery , " test " , " memory_cache " ,
" object_lifetime_maximum=3,full_backend_cache=yes " , 1 ) ;
ast_sorcery_apply_wizard_mapping ( sorcery , " test " , " mock " , NULL , 0 ) ;
ast_sorcery_internal_object_register ( sorcery , " test " , test_data_alloc , NULL , NULL ) ;
ast_sorcery_object_field_register_nodoc ( sorcery , " test " , " salt " , " 0 " , OPT_UINT_T , 0 , FLDSET ( struct test_data , salt ) ) ;
ast_sorcery_object_field_register_nodoc ( sorcery , " test " , " pepper " , " 0 " , OPT_UINT_T , 0 , FLDSET ( struct test_data , pepper ) ) ;
/* Prepopulate the cache */
real_backend_data = & initial ;
/* Get all current objects in the backend */
objects = ast_sorcery_retrieve_by_fields ( sorcery , " test " , AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL , NULL ) ;
if ( ! objects ) {
ast_test_status_update ( test , " Unable to retrieve all objects in backend and populate cache \n " ) ;
goto cleanup ;
}
ao2_ref ( objects , - 1 ) ;
/* Update the backend to have a different number of objects */
initial . exists = 8 ;
/* Get all current objects in the backend */
objects = ast_sorcery_retrieve_by_fields ( sorcery , " test " , AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL , NULL ) ;
if ( ! objects ) {
ast_test_status_update ( test , " Unable to retrieve all objects in backend and populate cache \n " ) ;
goto cleanup ;
}
if ( ao2_container_count ( objects ) = = initial . exists ) {
ast_test_status_update ( test , " Number of objects returned is of the current backend and not the cache \n " ) ;
ao2_ref ( objects , - 1 ) ;
goto cleanup ;
}
ao2_ref ( objects , - 1 ) ;
ast_mutex_init ( & lock ) ;
ast_cond_init ( & cond , NULL ) ;
start = ast_tvnow ( ) ;
end . tv_sec = start . tv_sec + 5 ;
end . tv_nsec = start . tv_usec * 1000 ;
ast_mutex_lock ( & lock ) ;
while ( ast_cond_timedwait ( & cond , & lock , & end ) ! = ETIMEDOUT ) {
}
ast_mutex_unlock ( & lock ) ;
ast_mutex_destroy ( & lock ) ;
ast_cond_destroy ( & cond ) ;
/* Get all current objects in the backend */
objects = ast_sorcery_retrieve_by_fields ( sorcery , " test " , AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL , NULL ) ;
if ( ! objects ) {
ast_test_status_update ( test , " Unable to retrieve all objects in backend and populate cache \n " ) ;
goto cleanup ;
}
if ( ao2_container_count ( objects ) ! = initial . exists ) {
ast_test_status_update ( test , " Number of objects returned is NOT of the current backend when it should be \n " ) ;
ao2_ref ( objects , - 1 ) ;
goto cleanup ;
}
ao2_ref ( objects , - 1 ) ;
res = AST_TEST_PASS ;
cleanup :
if ( sorcery ) {
ast_sorcery_unref ( sorcery ) ;
}
ast_sorcery_wizard_unregister ( & mock_wizard ) ;
return res ;
}
AST_TEST_DEFINE ( full_backend_cache_stale )
{
int res = AST_TEST_FAIL ;
struct ast_sorcery * sorcery = NULL ;
struct backend_data initial = {
. salt = 0 ,
. pepper = 0 ,
. exists = 4 ,
} ;
struct ao2_container * objects ;
ast_mutex_t lock ;
ast_cond_t cond ;
struct timeval start ;
struct timespec end ;
switch ( cmd ) {
case TEST_INIT :
info - > name = " full_backend_cache_stale " ;
info - > category = " /res/res_sorcery_memory_cache/ " ;
info - > summary = " Ensure that the full backend cache works with staleness " ;
info - > description = " This test performs the following: \n "
" \t * Create a sorcery instance with two wizards "
" \t \t * The first is a memory cache that stales objects after 1 second and does full backend caching \n "
" \t \t * The second is a mock of a back-end \n "
" \t * Populates the cache by requesting all objects which returns 4. \n "
" \t * Wait for objects to go stale. \n "
" \t * Updates the backend to contain a different number of objects, 8. \" "
" \t * Requests all objects and confirms the number returned is only 4. \n "
" \t * Wait for objects to be refreshed from backend. \n "
" \t * Requests all objects and confirms the number returned is 8. " ;
return AST_TEST_NOT_RUN ;
case TEST_EXECUTE :
break ;
}
ast_sorcery_wizard_register ( & mock_wizard ) ;
ast_mutex_init ( & lock ) ;
ast_cond_init ( & cond , NULL ) ;
sorcery = ast_sorcery_open ( ) ;
if ( ! sorcery ) {
ast_test_status_update ( test , " Failed to create sorcery instance \n " ) ;
goto cleanup ;
}
ast_sorcery_apply_wizard_mapping ( sorcery , " test " , " memory_cache " ,
" object_lifetime_stale=1,full_backend_cache=yes " , 1 ) ;
ast_sorcery_apply_wizard_mapping ( sorcery , " test " , " mock " , NULL , 0 ) ;
ast_sorcery_internal_object_register ( sorcery , " test " , test_data_alloc , NULL , NULL ) ;
ast_sorcery_object_field_register_nodoc ( sorcery , " test " , " salt " , " 0 " , OPT_UINT_T , 0 , FLDSET ( struct test_data , salt ) ) ;
ast_sorcery_object_field_register_nodoc ( sorcery , " test " , " pepper " , " 0 " , OPT_UINT_T , 0 , FLDSET ( struct test_data , pepper ) ) ;
/* Prepopulate the cache */
real_backend_data = & initial ;
/* Get all current objects in the backend */
objects = ast_sorcery_retrieve_by_fields ( sorcery , " test " , AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL , NULL ) ;
if ( ! objects ) {
ast_test_status_update ( test , " Unable to retrieve all objects in backend and populate cache \n " ) ;
goto cleanup ;
}
ao2_ref ( objects , - 1 ) ;
start = ast_tvnow ( ) ;
end . tv_sec = start . tv_sec + 5 ;
end . tv_nsec = start . tv_usec * 1000 ;
ast_mutex_lock ( & lock ) ;
while ( ast_cond_timedwait ( & cond , & lock , & end ) ! = ETIMEDOUT ) {
}
ast_mutex_unlock ( & lock ) ;
initial . exists = 8 ;
/* Get all current objects in the backend */
objects = ast_sorcery_retrieve_by_fields ( sorcery , " test " , AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL , NULL ) ;
if ( ! objects ) {
ast_test_status_update ( test , " Unable to retrieve all objects in backend and populate cache \n " ) ;
goto cleanup ;
}
if ( ao2_container_count ( objects ) = = initial . exists ) {
ast_test_status_update ( test , " Number of objects returned is of the backend and not the cache \n " ) ;
ao2_ref ( objects , - 1 ) ;
goto cleanup ;
}
ao2_ref ( objects , - 1 ) ;
start = ast_tvnow ( ) ;
end . tv_sec = start . tv_sec + 5 ;
end . tv_nsec = start . tv_usec * 1000 ;
ast_mutex_lock ( & lock ) ;
while ( ast_cond_timedwait ( & cond , & lock , & end ) ! = ETIMEDOUT ) {
}
ast_mutex_unlock ( & lock ) ;
/* Get all current objects in the backend */
objects = ast_sorcery_retrieve_by_fields ( sorcery , " test " , AST_RETRIEVE_FLAG_MULTIPLE | AST_RETRIEVE_FLAG_ALL , NULL ) ;
if ( ! objects ) {
ast_test_status_update ( test , " Unable to retrieve all objects in backend and populate cache \n " ) ;
goto cleanup ;
}
if ( ao2_container_count ( objects ) ! = initial . exists ) {
ast_test_status_update ( test , " Number of objects returned is not of backend \n " ) ;
ao2_ref ( objects , - 1 ) ;
goto cleanup ;
}
ao2_ref ( objects , - 1 ) ;
start = ast_tvnow ( ) ;
end . tv_sec = start . tv_sec + 5 ;
end . tv_nsec = start . tv_usec * 1000 ;
ast_mutex_lock ( & lock ) ;
while ( ast_cond_timedwait ( & cond , & lock , & end ) ! = ETIMEDOUT ) {
}
ast_mutex_unlock ( & lock ) ;
res = AST_TEST_PASS ;
cleanup :
if ( sorcery ) {
ast_sorcery_unref ( sorcery ) ;
}
ast_sorcery_wizard_unregister ( & mock_wizard ) ;
ast_mutex_destroy ( & lock ) ;
ast_cond_destroy ( & cond ) ;
return res ;
}
2015-05-16 19:02:50 -03:00
# endif
static int unload_module ( void )
{
AST_TEST_UNREGISTER ( open_with_valid_options ) ;
AST_TEST_UNREGISTER ( open_with_invalid_options ) ;
AST_TEST_UNREGISTER ( create_and_retrieve ) ;
AST_TEST_UNREGISTER ( update ) ;
AST_TEST_UNREGISTER ( delete ) ;
2015-05-20 15:19:27 -05:00
AST_TEST_UNREGISTER ( maximum_objects ) ;
2015-05-20 19:35:54 -03:00
AST_TEST_UNREGISTER ( expiration ) ;
2015-05-26 13:01:24 -05:00
AST_TEST_UNREGISTER ( stale ) ;
2015-12-05 12:01:55 -04:00
AST_TEST_UNREGISTER ( full_backend_cache_expiration ) ;
AST_TEST_UNREGISTER ( full_backend_cache_stale ) ;
2015-05-16 19:02:50 -03:00
2015-10-01 14:27:34 -05:00
ast_manager_unregister ( " SorceryMemoryCacheExpireObject " ) ;
ast_manager_unregister ( " SorceryMemoryCacheExpire " ) ;
ast_manager_unregister ( " SorceryMemoryCacheStaleObject " ) ;
ast_manager_unregister ( " SorceryMemoryCacheStale " ) ;
2015-12-05 12:01:55 -04:00
ast_manager_unregister ( " SorceryMemoryCachePopulate " ) ;
2015-10-01 14:27:34 -05:00
ast_cli_unregister_multiple ( cli_memory_cache , ARRAY_LEN ( cli_memory_cache ) ) ;
ast_sorcery_wizard_unregister ( & memory_cache_object_wizard ) ;
/*
* XXX There is the potential to leak memory if there are pending
* next-cache-expiration and stale-cache-update tasks in the scheduler.
*/
if ( sched ) {
ast_sched_context_destroy ( sched ) ;
sched = NULL ;
}
ao2_cleanup ( caches ) ;
caches = NULL ;
2015-05-16 19:02:50 -03:00
return 0 ;
}
static int load_module ( void )
{
2015-05-26 09:34:47 -03:00
int res ;
2018-11-19 15:10:02 -05:00
caches = ao2_container_alloc_hash ( AO2_ALLOC_OPT_LOCK_MUTEX , 0 ,
CACHES_CONTAINER_BUCKET_SIZE , sorcery_memory_cache_hash , NULL ,
2015-10-01 14:27:34 -05:00
sorcery_memory_cache_cmp ) ;
if ( ! caches ) {
ast_log ( LOG_ERROR , " Failed to create container for configured caches \n " ) ;
unload_module ( ) ;
return AST_MODULE_LOAD_DECLINE ;
}
2015-05-16 19:02:50 -03:00
sched = ast_sched_context_create ( ) ;
if ( ! sched ) {
ast_log ( LOG_ERROR , " Failed to create scheduler for cache management \n " ) ;
unload_module ( ) ;
return AST_MODULE_LOAD_DECLINE ;
}
if ( ast_sched_start_thread ( sched ) ) {
ast_log ( LOG_ERROR , " Failed to create scheduler thread for cache management \n " ) ;
unload_module ( ) ;
return AST_MODULE_LOAD_DECLINE ;
}
if ( ast_sorcery_wizard_register ( & memory_cache_object_wizard ) ) {
unload_module ( ) ;
return AST_MODULE_LOAD_DECLINE ;
}
2015-05-26 09:34:47 -03:00
res = ast_cli_register_multiple ( cli_memory_cache , ARRAY_LEN ( cli_memory_cache ) ) ;
res | = ast_manager_register_xml ( " SorceryMemoryCacheExpireObject " , EVENT_FLAG_SYSTEM , sorcery_memory_cache_ami_expire_object ) ;
res | = ast_manager_register_xml ( " SorceryMemoryCacheExpire " , EVENT_FLAG_SYSTEM , sorcery_memory_cache_ami_expire ) ;
res | = ast_manager_register_xml ( " SorceryMemoryCacheStaleObject " , EVENT_FLAG_SYSTEM , sorcery_memory_cache_ami_stale_object ) ;
res | = ast_manager_register_xml ( " SorceryMemoryCacheStale " , EVENT_FLAG_SYSTEM , sorcery_memory_cache_ami_stale ) ;
2015-12-05 12:01:55 -04:00
res | = ast_manager_register_xml ( " SorceryMemoryCachePopulate " , EVENT_FLAG_SYSTEM , sorcery_memory_cache_ami_populate ) ;
2015-05-26 09:34:47 -03:00
if ( res ) {
unload_module ( ) ;
return AST_MODULE_LOAD_DECLINE ;
}
2015-07-06 11:24:43 -03:00
/* This causes the stale unit test to execute last, so if a sorcery instance persists
* longer than expected subsequent unit tests don't fail when setting it up.
*/
AST_TEST_REGISTER ( stale ) ;
2015-05-16 19:02:50 -03:00
AST_TEST_REGISTER ( open_with_valid_options ) ;
AST_TEST_REGISTER ( open_with_invalid_options ) ;
AST_TEST_REGISTER ( create_and_retrieve ) ;
AST_TEST_REGISTER ( update ) ;
AST_TEST_REGISTER ( delete ) ;
2015-05-20 15:19:27 -05:00
AST_TEST_REGISTER ( maximum_objects ) ;
2015-05-20 19:35:54 -03:00
AST_TEST_REGISTER ( expiration ) ;
2015-12-05 12:01:55 -04:00
AST_TEST_REGISTER ( full_backend_cache_expiration ) ;
AST_TEST_REGISTER ( full_backend_cache_stale ) ;
2015-05-16 19:02:50 -03:00
return AST_MODULE_LOAD_SUCCESS ;
}
AST_MODULE_INFO ( ASTERISK_GPL_KEY , AST_MODFLAG_GLOBAL_SYMBOLS | AST_MODFLAG_LOAD_ORDER , " Sorcery Memory Cache Object Wizard " ,
. support_level = AST_MODULE_SUPPORT_CORE ,
. load = load_module ,
. unload = unload_module ,
. load_pri = AST_MODPRI_REALTIME_DRIVER ,
) ;