diff --git a/include/asterisk/astobj2.h b/include/asterisk/astobj2.h index 0b18f43dde..49b2a05f20 100644 --- a/include/asterisk/astobj2.h +++ b/include/asterisk/astobj2.h @@ -776,6 +776,58 @@ struct ao2_container *__ao2_container_alloc_debug(unsigned int n_buckets, */ int ao2_container_count(struct ao2_container *c); +/*! + * \brief Copy all object references in the src container into the dest container. + * \since 11.0 + * + * \param dest Container to copy src object references into. + * \param src Container to copy all object references from. + * \param flags OBJ_NOLOCK if a lock is already held on both containers. + * Otherwise, the src container is locked first. + * + * \pre The dest container must be empty. If the duplication fails, the + * dest container will be returned empty. + * + * \note This can potentially be expensive because a malloc is + * needed for every object in the src container. + * + * \retval 0 on success. + * \retval -1 on error. + */ +int ao2_container_dup(struct ao2_container *dest, struct ao2_container *src, enum search_flags flags); + +/*! + * \brief Create a clone/copy of the given container. + * \since 11.0 + * + * \param orig Container to copy all object references from. + * \param flags OBJ_NOLOCK if a lock is already held on the container. + * + * \note This can potentially be expensive because a malloc is + * needed for every object in the orig container. + * + * \retval Clone container on success. + * \retval NULL on error. + */ +struct ao2_container *__ao2_container_clone(struct ao2_container *orig, enum search_flags flags); +struct ao2_container *__ao2_container_clone_debug(struct ao2_container *orig, enum search_flags flags, const char *tag, char *file, int line, const char *funcname, int ref_debug); +#if defined(REF_DEBUG) + +#define ao2_t_container_clone(orig, flags, tag) __ao2_container_clone_debug(orig, flags, tag, __FILE__, __LINE__, __PRETTY_FUNCTION__, 1) +#define ao2_container_clone(orig, flags) __ao2_container_clone_debug(orig, flags, "", __FILE__, __LINE__, __PRETTY_FUNCTION__, 1) + +#elif defined(__AST_DEBUG_MALLOC) + +#define ao2_t_container_clone(orig, flags, tag) __ao2_container_clone_debug(orig, flags, tag, __FILE__, __LINE__, __PRETTY_FUNCTION__, 0) +#define ao2_container_clone(orig, flags) __ao2_container_clone_debug(orig, flags, "", __FILE__, __LINE__, __PRETTY_FUNCTION__, 0) + +#else + +#define ao2_t_container_clone(orig, flags, tag) __ao2_container_clone(orig, flags) +#define ao2_container_clone(orig, flags) __ao2_container_clone(orig, flags) + +#endif + /*@} */ /*! \name Object Management diff --git a/main/astobj2.c b/main/astobj2.c index 38f3d1b390..a93a342e01 100644 --- a/main/astobj2.c +++ b/main/astobj2.c @@ -27,6 +27,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$") #include "asterisk/cli.h" #define REF_FILE "/tmp/refs" +#if defined(TEST_FRAMEWORK) +/* We are building with the test framework enabled so enable AO2 debug tests as well. */ +#define AO2_DEBUG 1 +#endif /* defined(TEST_FRAMEWORK) */ + /*! * astobj2 objects are always preceded by this data structure, * which contains a lock, a reference counter, @@ -1007,6 +1012,109 @@ static void container_destruct_debug(void *_c) #endif } +/*! + * \internal + * \brief Put obj into the arg container. + * \since 11.0 + * + * \param obj pointer to the (user-defined part) of an object. + * \param arg callback argument from ao2_callback() + * \param flags flags from ao2_callback() + * + * \retval 0 on success. + * \retval CMP_STOP|CMP_MATCH on error. + */ +static int dup_obj_cb(void *obj, void *arg, int flags) +{ + struct ao2_container *dest = arg; + + return __ao2_link(dest, obj, OBJ_NOLOCK) ? 0 : (CMP_MATCH | CMP_STOP); +} + +int ao2_container_dup(struct ao2_container *dest, struct ao2_container *src, enum search_flags flags) +{ + void *obj; + int res = 0; + + if (!(flags & OBJ_NOLOCK)) { + ao2_lock(src); + ao2_lock(dest); + } + obj = __ao2_callback(src, OBJ_NOLOCK, dup_obj_cb, dest); + if (obj) { + /* Failed to put this obj into the dest container. */ + __ao2_ref(obj, -1); + + /* Remove all items from the dest container. */ + __ao2_callback(dest, OBJ_NOLOCK | OBJ_UNLINK | OBJ_NODATA | OBJ_MULTIPLE, NULL, + NULL); + res = -1; + } + if (!(flags & OBJ_NOLOCK)) { + ao2_unlock(dest); + ao2_unlock(src); + } + + return res; +} + +struct ao2_container *__ao2_container_clone(struct ao2_container *orig, enum search_flags flags) +{ + struct ao2_container *clone; + int failed; + + /* Create the clone container with the same properties as the original. */ + clone = __ao2_container_alloc(orig->n_buckets, orig->hash_fn, orig->cmp_fn); + if (!clone) { + return NULL; + } + + if (flags & OBJ_NOLOCK) { + ao2_lock(clone); + } + failed = ao2_container_dup(clone, orig, flags); + if (flags & OBJ_NOLOCK) { + ao2_unlock(clone); + } + if (failed) { + /* Object copy into the clone container failed. */ + __ao2_ref(clone, -1); + clone = NULL; + } + return clone; +} + +struct ao2_container *__ao2_container_clone_debug(struct ao2_container *orig, enum search_flags flags, const char *tag, char *file, int line, const char *funcname, int ref_debug) +{ + struct ao2_container *clone; + int failed; + + /* Create the clone container with the same properties as the original. */ + clone = __ao2_container_alloc_debug(orig->n_buckets, orig->hash_fn, orig->cmp_fn, tag, + file, line, funcname, ref_debug); + if (!clone) { + return NULL; + } + + if (flags & OBJ_NOLOCK) { + ao2_lock(clone); + } + failed = ao2_container_dup(clone, orig, flags); + if (flags & OBJ_NOLOCK) { + ao2_unlock(clone); + } + if (failed) { + /* Object copy into the clone container failed. */ + if (ref_debug) { + __ao2_ref_debug(clone, -1, tag, file, line, funcname); + } else { + __ao2_ref(clone, -1); + } + clone = NULL; + } + return clone; +} + #ifdef AO2_DEBUG static int print_cb(void *obj, void *arg, int flag) { @@ -1045,6 +1153,7 @@ static char *handle_astobj2_stats(struct ast_cli_entry *e, int cmd, struct ast_c 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; @@ -1099,8 +1208,17 @@ static char *handle_astobj2_test(struct ast_cli_entry *e, int cmd, struct ast_cl */ 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; @@ -1122,6 +1240,7 @@ static char *handle_astobj2_test(struct ast_cli_entry *e, int cmd, struct ast_cl } ao2_iterator_destroy(&ai); } + ast_cli(a->fd, "testing callbacks again\n"); ao2_t_callback(c1, 0, print_cb, a, "test callback"); @@ -1130,6 +1249,7 @@ static char *handle_astobj2_test(struct ast_cli_entry *e, int cmd, struct ast_cl 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; } diff --git a/tests/test_astobj2.c b/tests/test_astobj2.c index 860c569575..05b91324fd 100644 --- a/tests/test_astobj2.c +++ b/tests/test_astobj2.c @@ -109,9 +109,11 @@ static int astobj2_test_helper(int use_hash, int use_cmp, unsigned int lim, stru { struct ao2_container *c1; struct ao2_container *c2; + struct ao2_container *c3 = NULL; struct ao2_iterator it; struct ao2_iterator *mult_it; struct test_obj *obj; + struct test_obj *obj2; struct test_obj tmp_obj; int bucket_size; int increment = 0; @@ -154,6 +156,44 @@ static int astobj2_test_helper(int use_hash, int use_cmp, unsigned int lim, stru ast_test_status_update(test, "Container created: random bucket size %d: number of items: %d\n", bucket_size, lim); + /* Testing ao2_container_clone */ + c3 = ao2_container_clone(c1, 0); + if (!c3) { + ast_test_status_update(test, "ao2_container_clone failed.\n"); + res = AST_TEST_FAIL; + goto cleanup; + } + if (ao2_container_count(c1) != ao2_container_count(c3)) { + ast_test_status_update(test, "Cloned container does not have the same number of objects.\n"); + res = AST_TEST_FAIL; + } else { + it = ao2_iterator_init(c1, 0); + for (; (obj = ao2_t_iterator_next(&it, "test orig")); ao2_t_ref(obj, -1, "test orig")) { + /* + * Unlink the matching object from the cloned container to make + * the next search faster. This is a big speed optimization! + * It reduces the container with 100000 objects test time from + * 18 seconds to 200 ms. + */ + obj2 = ao2_t_callback(c3, OBJ_POINTER | OBJ_UNLINK, ao2_match_by_addr, obj, + "test clone"); + if (obj2) { + ao2_t_ref(obj2, -1, "test clone"); + continue; + } + ast_test_status_update(test, + "Orig container has an object %p not in the clone container.\n", obj); + res = AST_TEST_FAIL; + } + ao2_iterator_destroy(&it); + if (ao2_container_count(c3)) { + ast_test_status_update(test, "Cloned container still has objects.\n"); + res = AST_TEST_FAIL; + } + } + ao2_t_ref(c3, -1, "bye c3"); + c3 = NULL; + /* Testing ao2_find with no flags */ num = 100; for (; num; num--) { @@ -336,6 +376,9 @@ cleanup: if (c2) { ao2_t_ref(c2, -1, "bye c2"); } + if (c3) { + ao2_t_ref(c3, -1, "bye c3"); + } if (destructor_count > 0) { ast_test_status_update(test, "all destructors were not called, destructor count is %d\n", destructor_count);