/* * astobj2 - replacement containers for asterisk data structures. * * Copyright (C) 2006 Marta Carbone, Luigi Rizzo - Univ. di Pisa, Italy * * 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 Functions implementing astobj2 objects. * * \author Richard Mudgett */ /*** MODULEINFO core ***/ #include "asterisk.h" ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/_private.h" #include "asterisk/astobj2.h" #include "astobj2_private.h" #include "astobj2_container_private.h" #include "asterisk/cli.h" #include "asterisk/paths.h" /* Use ast_log_safe in place of ast_log. */ #define ast_log ast_log_safe static FILE *ref_log; /*! * astobj2 objects are always preceded by this data structure, * which contains a reference counter, * option flags and a pointer to a destructor. * The refcount is used to decide when it is time to * invoke the destructor. * The magic number is used for consistency check. */ struct __priv_data { int ref_counter; ao2_destructor_fn destructor_fn; /*! User data size for stats */ size_t data_size; /*! The ao2 object option flags */ uint32_t options; /*! magic number. This is used to verify that a pointer passed in is a * valid astobj2 */ uint32_t magic; }; #define AO2_MAGIC 0xa570b123 /*! * What an astobj2 object looks like: fixed-size private data * followed by variable-size user data. */ struct astobj2 { struct __priv_data priv_data; void *user_data[0]; }; struct ao2_lock_priv { ast_mutex_t lock; }; /* AstObj2 with recursive lock. */ struct astobj2_lock { struct ao2_lock_priv mutex; struct __priv_data priv_data; void *user_data[0]; }; struct ao2_rwlock_priv { ast_rwlock_t lock; /*! Count of the number of threads holding a lock on this object. -1 if it is the write lock. */ int num_lockers; }; /* AstObj2 with RW lock. */ struct astobj2_rwlock { struct ao2_rwlock_priv rwlock; struct __priv_data priv_data; void *user_data[0]; }; #ifdef AO2_DEBUG struct ao2_stats ao2; #endif #define INTERNAL_OBJ_MUTEX(user_data) \ ((struct astobj2_lock *) (((char *) (user_data)) - sizeof(struct astobj2_lock))) #define INTERNAL_OBJ_RWLOCK(user_data) \ ((struct astobj2_rwlock *) (((char *) (user_data)) - sizeof(struct astobj2_rwlock))) /*! * \brief convert from a pointer _p to a user-defined object * * \return the pointer to the astobj2 structure */ static struct astobj2 *INTERNAL_OBJ(void *user_data) { struct astobj2 *p; if (!user_data) { __ast_assert_failed(0, "user_data is NULL", __FILE__, __LINE__, __PRETTY_FUNCTION__); return NULL; } p = (struct astobj2 *) ((char *) user_data - sizeof(*p)); if (AO2_MAGIC != p->priv_data.magic) { char bad_magic[100]; snprintf(bad_magic, sizeof(bad_magic), "bad magic number 0x%x for object %p", p->priv_data.magic, user_data); __ast_assert_failed(0, bad_magic, __FILE__, __LINE__, __PRETTY_FUNCTION__); return NULL; } return p; } /*! * \brief convert from a pointer _p to an astobj2 object * * \return the pointer to the user-defined portion. */ #define EXTERNAL_OBJ(_p) ((_p) == NULL ? NULL : (_p)->user_data) int is_ao2_object(void *user_data) { return (INTERNAL_OBJ(user_data) != NULL); } int __ao2_lock(void *user_data, enum ao2_lock_req lock_how, const char *file, const char *func, int line, const char *var) { struct astobj2 *obj = INTERNAL_OBJ(user_data); struct astobj2_lock *obj_mutex; struct astobj2_rwlock *obj_rwlock; int res = 0; if (obj == NULL) { return -1; } switch (obj->priv_data.options & AO2_ALLOC_OPT_LOCK_MASK) { case AO2_ALLOC_OPT_LOCK_MUTEX: obj_mutex = INTERNAL_OBJ_MUTEX(user_data); res = __ast_pthread_mutex_lock(file, line, func, var, &obj_mutex->mutex.lock); #ifdef AO2_DEBUG if (!res) { ast_atomic_fetchadd_int(&ao2.total_locked, 1); } #endif break; case AO2_ALLOC_OPT_LOCK_RWLOCK: obj_rwlock = INTERNAL_OBJ_RWLOCK(user_data); switch (lock_how) { case AO2_LOCK_REQ_MUTEX: case AO2_LOCK_REQ_WRLOCK: res = __ast_rwlock_wrlock(file, line, func, &obj_rwlock->rwlock.lock, var); if (!res) { ast_atomic_fetchadd_int(&obj_rwlock->rwlock.num_lockers, -1); #ifdef AO2_DEBUG ast_atomic_fetchadd_int(&ao2.total_locked, 1); #endif } break; case AO2_LOCK_REQ_RDLOCK: res = __ast_rwlock_rdlock(file, line, func, &obj_rwlock->rwlock.lock, var); if (!res) { ast_atomic_fetchadd_int(&obj_rwlock->rwlock.num_lockers, +1); #ifdef AO2_DEBUG ast_atomic_fetchadd_int(&ao2.total_locked, 1); #endif } break; } break; case AO2_ALLOC_OPT_LOCK_NOLOCK: /* The ao2 object has no lock. */ break; default: ast_log(__LOG_ERROR, file, line, func, "Invalid lock option on ao2 object %p\n", user_data); return -1; } return res; } int __ao2_unlock(void *user_data, const char *file, const char *func, int line, const char *var) { struct astobj2 *obj = INTERNAL_OBJ(user_data); struct astobj2_lock *obj_mutex; struct astobj2_rwlock *obj_rwlock; int res = 0; int current_value; if (obj == NULL) { return -1; } switch (obj->priv_data.options & AO2_ALLOC_OPT_LOCK_MASK) { case AO2_ALLOC_OPT_LOCK_MUTEX: obj_mutex = INTERNAL_OBJ_MUTEX(user_data); res = __ast_pthread_mutex_unlock(file, line, func, var, &obj_mutex->mutex.lock); #ifdef AO2_DEBUG if (!res) { ast_atomic_fetchadd_int(&ao2.total_locked, -1); } #endif break; case AO2_ALLOC_OPT_LOCK_RWLOCK: obj_rwlock = INTERNAL_OBJ_RWLOCK(user_data); current_value = ast_atomic_fetchadd_int(&obj_rwlock->rwlock.num_lockers, -1) - 1; if (current_value < 0) { /* It was a WRLOCK that we are unlocking. Fix the count. */ ast_atomic_fetchadd_int(&obj_rwlock->rwlock.num_lockers, -current_value); } res = __ast_rwlock_unlock(file, line, func, &obj_rwlock->rwlock.lock, var); #ifdef AO2_DEBUG if (!res) { ast_atomic_fetchadd_int(&ao2.total_locked, -1); } #endif break; case AO2_ALLOC_OPT_LOCK_NOLOCK: /* The ao2 object has no lock. */ break; default: ast_log(__LOG_ERROR, file, line, func, "Invalid lock option on ao2 object %p\n", user_data); res = -1; break; } return res; } int __ao2_trylock(void *user_data, enum ao2_lock_req lock_how, const char *file, const char *func, int line, const char *var) { struct astobj2 *obj = INTERNAL_OBJ(user_data); struct astobj2_lock *obj_mutex; struct astobj2_rwlock *obj_rwlock; int res = 0; if (obj == NULL) { return -1; } switch (obj->priv_data.options & AO2_ALLOC_OPT_LOCK_MASK) { case AO2_ALLOC_OPT_LOCK_MUTEX: obj_mutex = INTERNAL_OBJ_MUTEX(user_data); res = __ast_pthread_mutex_trylock(file, line, func, var, &obj_mutex->mutex.lock); #ifdef AO2_DEBUG if (!res) { ast_atomic_fetchadd_int(&ao2.total_locked, 1); } #endif break; case AO2_ALLOC_OPT_LOCK_RWLOCK: obj_rwlock = INTERNAL_OBJ_RWLOCK(user_data); switch (lock_how) { case AO2_LOCK_REQ_MUTEX: case AO2_LOCK_REQ_WRLOCK: res = __ast_rwlock_trywrlock(file, line, func, &obj_rwlock->rwlock.lock, var); if (!res) { ast_atomic_fetchadd_int(&obj_rwlock->rwlock.num_lockers, -1); #ifdef AO2_DEBUG ast_atomic_fetchadd_int(&ao2.total_locked, 1); #endif } break; case AO2_LOCK_REQ_RDLOCK: res = __ast_rwlock_tryrdlock(file, line, func, &obj_rwlock->rwlock.lock, var); if (!res) { ast_atomic_fetchadd_int(&obj_rwlock->rwlock.num_lockers, +1); #ifdef AO2_DEBUG ast_atomic_fetchadd_int(&ao2.total_locked, 1); #endif } break; } break; case AO2_ALLOC_OPT_LOCK_NOLOCK: /* The ao2 object has no lock. */ return 0; default: ast_log(__LOG_ERROR, file, line, func, "Invalid lock option on ao2 object %p\n", user_data); return -1; } return res; } /*! * \internal * \brief Adjust an object's lock to the requested level. * * \param user_data An ao2 object to adjust lock level. * \param lock_how What level to adjust lock. * \param keep_stronger TRUE if keep original lock level if it is stronger. * * \pre The ao2 object is already locked. * * \details * An ao2 object with a RWLOCK will have its lock level adjusted * to the specified level if it is not already there. An ao2 * object with a different type of lock is not affected. * * \return Original lock level. */ enum ao2_lock_req __adjust_lock(void *user_data, enum ao2_lock_req lock_how, int keep_stronger) { struct astobj2 *obj = INTERNAL_OBJ(user_data); struct astobj2_rwlock *obj_rwlock; enum ao2_lock_req orig_lock; switch (obj->priv_data.options & AO2_ALLOC_OPT_LOCK_MASK) { case AO2_ALLOC_OPT_LOCK_RWLOCK: obj_rwlock = INTERNAL_OBJ_RWLOCK(user_data); if (obj_rwlock->rwlock.num_lockers < 0) { orig_lock = AO2_LOCK_REQ_WRLOCK; } else { orig_lock = AO2_LOCK_REQ_RDLOCK; } switch (lock_how) { case AO2_LOCK_REQ_MUTEX: lock_how = AO2_LOCK_REQ_WRLOCK; /* Fall through */ case AO2_LOCK_REQ_WRLOCK: if (lock_how != orig_lock) { /* Switch from read lock to write lock. */ ao2_unlock(user_data); ao2_wrlock(user_data); } break; case AO2_LOCK_REQ_RDLOCK: if (!keep_stronger && lock_how != orig_lock) { /* Switch from write lock to read lock. */ ao2_unlock(user_data); ao2_rdlock(user_data); } break; } break; default: ast_log(LOG_ERROR, "Invalid lock option on ao2 object %p\n", user_data); /* Fall through */ case AO2_ALLOC_OPT_LOCK_NOLOCK: case AO2_ALLOC_OPT_LOCK_MUTEX: orig_lock = AO2_LOCK_REQ_MUTEX; break; } return orig_lock; } void *ao2_object_get_lockaddr(void *user_data) { struct astobj2 *obj = INTERNAL_OBJ(user_data); struct astobj2_lock *obj_mutex; if (obj == NULL) { return NULL; } switch (obj->priv_data.options & AO2_ALLOC_OPT_LOCK_MASK) { case AO2_ALLOC_OPT_LOCK_MUTEX: obj_mutex = INTERNAL_OBJ_MUTEX(user_data); return &obj_mutex->mutex.lock; default: break; } return NULL; } static int internal_ao2_ref(void *user_data, int delta, const char *file, int line, const char *func) { struct astobj2 *obj = INTERNAL_OBJ(user_data); struct astobj2_lock *obj_mutex; struct astobj2_rwlock *obj_rwlock; int current_value; int ret; if (obj == NULL) { return -1; } /* if delta is 0, just return the refcount */ if (delta == 0) { return obj->priv_data.ref_counter; } /* we modify with an atomic operation the reference counter */ ret = ast_atomic_fetchadd_int(&obj->priv_data.ref_counter, delta); current_value = ret + delta; #ifdef AO2_DEBUG ast_atomic_fetchadd_int(&ao2.total_refs, delta); #endif if (0 < current_value) { /* The object still lives. */ return ret; } /* this case must never happen */ if (current_value < 0) { ast_log(__LOG_ERROR, file, line, func, "Invalid refcount %d on ao2 object %p\n", current_value, user_data); ast_assert(0); /* stop here even if assert doesn't DO_CRASH */ return -1; } /* last reference, destroy the object */ if (obj->priv_data.destructor_fn != NULL) { obj->priv_data.destructor_fn(user_data); } #ifdef AO2_DEBUG ast_atomic_fetchadd_int(&ao2.total_mem, - obj->priv_data.data_size); ast_atomic_fetchadd_int(&ao2.total_objects, -1); #endif /* In case someone uses an object after it's been freed */ obj->priv_data.magic = 0; switch (obj->priv_data.options & AO2_ALLOC_OPT_LOCK_MASK) { case AO2_ALLOC_OPT_LOCK_MUTEX: obj_mutex = INTERNAL_OBJ_MUTEX(user_data); ast_mutex_destroy(&obj_mutex->mutex.lock); ast_free(obj_mutex); break; case AO2_ALLOC_OPT_LOCK_RWLOCK: obj_rwlock = INTERNAL_OBJ_RWLOCK(user_data); ast_rwlock_destroy(&obj_rwlock->rwlock.lock); ast_free(obj_rwlock); break; case AO2_ALLOC_OPT_LOCK_NOLOCK: ast_free(obj); break; default: ast_log(__LOG_ERROR, file, line, func, "Invalid lock option on ao2 object %p\n", user_data); break; } return ret; } int __ao2_ref_debug(void *user_data, int delta, const char *tag, const char *file, int line, const char *func) { struct astobj2 *obj = INTERNAL_OBJ(user_data); int old_refcount = -1; if (obj) { old_refcount = internal_ao2_ref(user_data, delta, file, line, func); } if (ref_log && user_data) { if (!obj) { /* Invalid object: Bad magic number. */ fprintf(ref_log, "%p,%d,%d,%s,%d,%s,**invalid**,%s\n", user_data, delta, ast_get_tid(), file, line, func, tag); fflush(ref_log); } else if (old_refcount + delta == 0) { fprintf(ref_log, "%p,%d,%d,%s,%d,%s,**destructor**,%s\n", user_data, delta, ast_get_tid(), file, line, func, tag); fflush(ref_log); } else if (delta != 0) { fprintf(ref_log, "%p,%s%d,%d,%s,%d,%s,%d,%s\n", user_data, (delta < 0 ? "" : "+"), delta, ast_get_tid(), file, line, func, old_refcount, tag); fflush(ref_log); } } return old_refcount; } int __ao2_ref(void *user_data, int delta) { return internal_ao2_ref(user_data, delta, __FILE__, __LINE__, __FUNCTION__); } void __ao2_cleanup_debug(void *obj, const char *tag, const char *file, int line, const char *function) { if (obj) { __ao2_ref_debug(obj, -1, tag, file, line, function); } } void __ao2_cleanup(void *obj) { if (obj) { ao2_ref(obj, -1); } } static void *internal_ao2_alloc(size_t data_size, ao2_destructor_fn destructor_fn, unsigned int options, const char *file, int line, const char *func) { /* allocation */ struct astobj2 *obj; struct astobj2_lock *obj_mutex; struct astobj2_rwlock *obj_rwlock; switch (options & AO2_ALLOC_OPT_LOCK_MASK) { case AO2_ALLOC_OPT_LOCK_MUTEX: #if defined(__AST_DEBUG_MALLOC) obj_mutex = __ast_calloc(1, sizeof(*obj_mutex) + data_size, file, line, func); #else obj_mutex = ast_calloc(1, sizeof(*obj_mutex) + data_size); #endif if (obj_mutex == NULL) { return NULL; } ast_mutex_init(&obj_mutex->mutex.lock); obj = (struct astobj2 *) &obj_mutex->priv_data; break; case AO2_ALLOC_OPT_LOCK_RWLOCK: #if defined(__AST_DEBUG_MALLOC) obj_rwlock = __ast_calloc(1, sizeof(*obj_rwlock) + data_size, file, line, func); #else obj_rwlock = ast_calloc(1, sizeof(*obj_rwlock) + data_size); #endif if (obj_rwlock == NULL) { return NULL; } ast_rwlock_init(&obj_rwlock->rwlock.lock); obj = (struct astobj2 *) &obj_rwlock->priv_data; break; case AO2_ALLOC_OPT_LOCK_NOLOCK: #if defined(__AST_DEBUG_MALLOC) obj = __ast_calloc(1, sizeof(*obj) + data_size, file, line, func); #else obj = ast_calloc(1, sizeof(*obj) + data_size); #endif if (obj == NULL) { return NULL; } break; default: /* Invalid option value. */ ast_log(__LOG_DEBUG, file, line, func, "Invalid lock option requested\n"); return NULL; } /* Initialize common ao2 values. */ obj->priv_data.ref_counter = 1; obj->priv_data.destructor_fn = destructor_fn; /* can be NULL */ obj->priv_data.data_size = data_size; obj->priv_data.options = options; obj->priv_data.magic = AO2_MAGIC; #ifdef AO2_DEBUG ast_atomic_fetchadd_int(&ao2.total_objects, 1); ast_atomic_fetchadd_int(&ao2.total_mem, data_size); ast_atomic_fetchadd_int(&ao2.total_refs, 1); #endif /* return a pointer to the user data */ return EXTERNAL_OBJ(obj); } unsigned int ao2_options_get(void *obj) { struct astobj2 *orig_obj = INTERNAL_OBJ(obj); if (!orig_obj) { return 0; } return orig_obj->priv_data.options; } void *__ao2_alloc_debug(size_t data_size, ao2_destructor_fn destructor_fn, unsigned int options, const char *tag, const char *file, int line, const char *func, int ref_debug) { /* allocation */ void *obj; if ((obj = internal_ao2_alloc(data_size, destructor_fn, options, file, line, func)) == NULL) { return NULL; } if (ref_log) { fprintf(ref_log, "%p,+1,%d,%s,%d,%s,**constructor**,%s\n", obj, ast_get_tid(), file, line, func, tag); fflush(ref_log); } /* return a pointer to the user data */ return obj; } void *__ao2_alloc(size_t data_size, ao2_destructor_fn destructor_fn, unsigned int options) { return internal_ao2_alloc(data_size, destructor_fn, options, __FILE__, __LINE__, __FUNCTION__); } void __ao2_global_obj_release(struct ao2_global_obj *holder, const char *tag, const char *file, int line, const char *func, const char *name) { if (!holder) { /* For sanity */ ast_log(LOG_ERROR, "Must be called with a global object!\n"); ast_assert(0); return; } if (__ast_rwlock_wrlock(file, line, func, &holder->lock, name)) { /* Could not get the write lock. */ ast_assert(0); return; } /* Release the held ao2 object. */ if (holder->obj) { if (tag) { __ao2_ref_debug(holder->obj, -1, tag, file, line, func); } else { __ao2_ref(holder->obj, -1); } holder->obj = NULL; } __ast_rwlock_unlock(file, line, func, &holder->lock, name); } void *__ao2_global_obj_replace(struct ao2_global_obj *holder, void *obj, const char *tag, const char *file, int line, const char *func, const char *name) { void *obj_old; if (!holder) { /* For sanity */ ast_log(LOG_ERROR, "Must be called with a global object!\n"); ast_assert(0); return NULL; } if (__ast_rwlock_wrlock(file, line, func, &holder->lock, name)) { /* Could not get the write lock. */ ast_assert(0); return NULL; } if (obj) { if (tag) { __ao2_ref_debug(obj, +1, tag, file, line, func); } else { __ao2_ref(obj, +1); } } obj_old = holder->obj; holder->obj = obj; __ast_rwlock_unlock(file, line, func, &holder->lock, name); return obj_old; } int __ao2_global_obj_replace_unref(struct ao2_global_obj *holder, void *obj, const char *tag, const char *file, int line, const char *func, const char *name) { void *obj_old; obj_old = __ao2_global_obj_replace(holder, obj, tag, file, line, func, name); if (obj_old) { if (tag) { __ao2_ref_debug(obj_old, -1, tag, file, line, func); } else { __ao2_ref(obj_old, -1); } return 1; } return 0; } void *__ao2_global_obj_ref(struct ao2_global_obj *holder, const char *tag, const char *file, int line, const char *func, const char *name) { void *obj; if (!holder) { /* For sanity */ ast_log(LOG_ERROR, "Must be called with a global object!\n"); ast_assert(0); return NULL; } if (__ast_rwlock_rdlock(file, line, func, &holder->lock, name)) { /* Could not get the read lock. */ ast_assert(0); return NULL; } obj = holder->obj; if (obj) { if (tag) { __ao2_ref_debug(obj, +1, tag, file, line, func); } else { __ao2_ref(obj, +1); } } __ast_rwlock_unlock(file, line, func, &holder->lock, name); return obj; } #ifdef AO2_DEBUG static int print_cb(void *obj, void *arg, int flag) { struct ast_cli_args *a = (struct ast_cli_args *) arg; char *s = (char *)obj; ast_cli(a->fd, "string <%s>\n", s); return 0; } /* * Print stats */ static char *handle_astobj2_stats(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { switch (cmd) { case CLI_INIT: e->command = "astobj2 show stats"; e->usage = "Usage: astobj2 show stats\n" " Show astobj2 show stats\n"; return NULL; case CLI_GENERATE: return NULL; } ast_cli(a->fd, "Objects : %d\n", ao2.total_objects); ast_cli(a->fd, "Containers : %d\n", ao2.total_containers); ast_cli(a->fd, "Memory : %d\n", ao2.total_mem); ast_cli(a->fd, "Locked : %d\n", ao2.total_locked); ast_cli(a->fd, "Refs : %d\n", ao2.total_refs); return CLI_SUCCESS; } /* * This is testing code for astobj */ static char *handle_astobj2_test(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a) { struct ao2_container *c1; struct ao2_container *c2; int i, lim; char *obj; static int prof_id = -1; struct ast_cli_args fake_args = { a->fd, 0, NULL }; switch (cmd) { case CLI_INIT: e->command = "astobj2 test"; e->usage = "Usage: astobj2 test \n" " Runs astobj2 test. Creates 'num' objects,\n" " and test iterators, callbacks and maybe other stuff\n"; return NULL; case CLI_GENERATE: return NULL; } if (a->argc != 3) { return CLI_SHOWUSAGE; } if (prof_id == -1) { prof_id = ast_add_profile("ao2_alloc", 0); } ast_cli(a->fd, "argc %d argv %s %s %s\n", a->argc, a->argv[0], a->argv[1], a->argv[2]); lim = atoi(a->argv[2]); ast_cli(a->fd, "called astobj_test\n"); handle_astobj2_stats(e, CLI_HANDLER, &fake_args); /* * Allocate a list container. */ c1 = ao2_t_container_alloc_list(AO2_ALLOC_OPT_LOCK_MUTEX, 0, NULL /* no sort */, NULL /* no callback */, "test"); ast_cli(a->fd, "container allocated as %p\n", c1); /* * fill the container with objects. * ao2_alloc() gives us a reference which we pass to the * container when we do the insert. */ for (i = 0; i < lim; i++) { ast_mark(prof_id, 1 /* start */); obj = ao2_t_alloc(80, NULL,"test"); ast_mark(prof_id, 0 /* stop */); ast_cli(a->fd, "object %d allocated as %p\n", i, obj); sprintf(obj, "-- this is obj %d --", i); ao2_link(c1, obj); /* At this point, the refcount on obj is 2 due to the allocation * and linking. We can go ahead and reduce the refcount by 1 * right here so that when the container is unreffed later, the * objects will be freed */ ao2_t_ref(obj, -1, "test"); } ast_cli(a->fd, "testing callbacks\n"); ao2_t_callback(c1, 0, print_cb, a, "test callback"); ast_cli(a->fd, "testing container cloning\n"); c2 = ao2_container_clone(c1, 0); if (ao2_container_count(c1) != ao2_container_count(c2)) { ast_cli(a->fd, "Cloned container does not have the same number of objects!\n"); } ao2_t_callback(c2, 0, print_cb, a, "test callback"); ast_cli(a->fd, "testing iterators, remove every second object\n"); { struct ao2_iterator ai; int x = 0; ai = ao2_iterator_init(c1, 0); while ( (obj = ao2_t_iterator_next(&ai,"test")) ) { ast_cli(a->fd, "iterator on <%s>\n", obj); if (x++ & 1) ao2_t_unlink(c1, obj,"test"); ao2_t_ref(obj, -1,"test"); } ao2_iterator_destroy(&ai); ast_cli(a->fd, "testing iterators again\n"); ai = ao2_iterator_init(c1, 0); while ( (obj = ao2_t_iterator_next(&ai,"test")) ) { ast_cli(a->fd, "iterator on <%s>\n", obj); ao2_t_ref(obj, -1,"test"); } ao2_iterator_destroy(&ai); } ast_cli(a->fd, "testing callbacks again\n"); ao2_t_callback(c1, 0, print_cb, a, "test callback"); ast_verbose("now you should see an error and possible assertion failure messages:\n"); ao2_t_ref(&i, -1, ""); /* i is not a valid object so we print an error here */ ast_cli(a->fd, "destroy container\n"); ao2_t_ref(c1, -1, ""); /* destroy container */ ao2_t_ref(c2, -1, ""); /* destroy container */ handle_astobj2_stats(e, CLI_HANDLER, &fake_args); return CLI_SUCCESS; } #endif /* AO2_DEBUG */ #if defined(AO2_DEBUG) static struct ast_cli_entry cli_astobj2[] = { AST_CLI_DEFINE(handle_astobj2_stats, "Print astobj2 statistics"), AST_CLI_DEFINE(handle_astobj2_test, "Test astobj2"), }; #endif /* AO2_DEBUG */ static void astobj2_cleanup(void) { #if defined(AO2_DEBUG) ast_cli_unregister_multiple(cli_astobj2, ARRAY_LEN(cli_astobj2)); #endif #ifdef REF_DEBUG fclose(ref_log); ref_log = NULL; #endif } int astobj2_init(void) { #ifdef REF_DEBUG char ref_filename[1024]; snprintf(ref_filename, sizeof(ref_filename), "%s/refs", ast_config_AST_LOG_DIR); ref_log = fopen(ref_filename, "w"); if (!ref_log) { ast_log(LOG_ERROR, "Could not open ref debug log file: %s\n", ref_filename); } #endif if (container_init() != 0) { fclose(ref_log); return -1; } #if defined(AO2_DEBUG) ast_cli_register_multiple(cli_astobj2, ARRAY_LEN(cli_astobj2)); #endif /* defined(AO2_DEBUG) */ ast_register_cleanup(astobj2_cleanup); return 0; }