Add ability to clone ao2 containers.
Occasionally there is a need to put all objects in one container also into another container. Some reasons you might need to do this: 1) You need to reconfigure a container. You would do this by creating a new container with the new configuration and ao2_container_dup the old container into it. Then replace the old container with the new. Then destroy the old container. 2) You need the contents of a container to remain stable while operating on all of the objects. You would do this by creating a cloned container of the original with ao2_container_clone. The cloned container is a snapshot of the objects at the time of the cloning. When done, just destroy the cloned container. Review: https://reviewboard.asterisk.org/r/1746/ git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@357145 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
parent
ae07610d73
commit
50c8557f03
|
@ -776,6 +776,58 @@ struct ao2_container *__ao2_container_alloc_debug(unsigned int n_buckets,
|
||||||
*/
|
*/
|
||||||
int ao2_container_count(struct ao2_container *c);
|
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
|
/*! \name Object Management
|
||||||
|
|
120
main/astobj2.c
120
main/astobj2.c
|
@ -27,6 +27,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
|
||||||
#include "asterisk/cli.h"
|
#include "asterisk/cli.h"
|
||||||
#define REF_FILE "/tmp/refs"
|
#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,
|
* astobj2 objects are always preceded by this data structure,
|
||||||
* which contains a lock, a reference counter,
|
* which contains a lock, a reference counter,
|
||||||
|
@ -1007,6 +1012,109 @@ static void container_destruct_debug(void *_c)
|
||||||
#endif
|
#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
|
#ifdef AO2_DEBUG
|
||||||
static int print_cb(void *obj, void *arg, int flag)
|
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)
|
static char *handle_astobj2_test(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
|
||||||
{
|
{
|
||||||
struct ao2_container *c1;
|
struct ao2_container *c1;
|
||||||
|
struct ao2_container *c2;
|
||||||
int i, lim;
|
int i, lim;
|
||||||
char *obj;
|
char *obj;
|
||||||
static int prof_id = -1;
|
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");
|
ao2_t_ref(obj, -1, "test");
|
||||||
}
|
}
|
||||||
|
|
||||||
ast_cli(a->fd, "testing callbacks\n");
|
ast_cli(a->fd, "testing callbacks\n");
|
||||||
ao2_t_callback(c1, 0, print_cb, a, "test callback");
|
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");
|
ast_cli(a->fd, "testing iterators, remove every second object\n");
|
||||||
{
|
{
|
||||||
struct ao2_iterator ai;
|
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);
|
ao2_iterator_destroy(&ai);
|
||||||
}
|
}
|
||||||
|
|
||||||
ast_cli(a->fd, "testing callbacks again\n");
|
ast_cli(a->fd, "testing callbacks again\n");
|
||||||
ao2_t_callback(c1, 0, print_cb, a, "test callback");
|
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");
|
ast_cli(a->fd, "destroy container\n");
|
||||||
ao2_t_ref(c1, -1, ""); /* destroy container */
|
ao2_t_ref(c1, -1, ""); /* destroy container */
|
||||||
|
ao2_t_ref(c2, -1, ""); /* destroy container */
|
||||||
handle_astobj2_stats(e, CLI_HANDLER, &fake_args);
|
handle_astobj2_stats(e, CLI_HANDLER, &fake_args);
|
||||||
return CLI_SUCCESS;
|
return CLI_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 *c1;
|
||||||
struct ao2_container *c2;
|
struct ao2_container *c2;
|
||||||
|
struct ao2_container *c3 = NULL;
|
||||||
struct ao2_iterator it;
|
struct ao2_iterator it;
|
||||||
struct ao2_iterator *mult_it;
|
struct ao2_iterator *mult_it;
|
||||||
struct test_obj *obj;
|
struct test_obj *obj;
|
||||||
|
struct test_obj *obj2;
|
||||||
struct test_obj tmp_obj;
|
struct test_obj tmp_obj;
|
||||||
int bucket_size;
|
int bucket_size;
|
||||||
int increment = 0;
|
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);
|
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 */
|
/* Testing ao2_find with no flags */
|
||||||
num = 100;
|
num = 100;
|
||||||
for (; num; num--) {
|
for (; num; num--) {
|
||||||
|
@ -336,6 +376,9 @@ cleanup:
|
||||||
if (c2) {
|
if (c2) {
|
||||||
ao2_t_ref(c2, -1, "bye c2");
|
ao2_t_ref(c2, -1, "bye c2");
|
||||||
}
|
}
|
||||||
|
if (c3) {
|
||||||
|
ao2_t_ref(c3, -1, "bye c3");
|
||||||
|
}
|
||||||
|
|
||||||
if (destructor_count > 0) {
|
if (destructor_count > 0) {
|
||||||
ast_test_status_update(test, "all destructors were not called, destructor count is %d\n", destructor_count);
|
ast_test_status_update(test, "all destructors were not called, destructor count is %d\n", destructor_count);
|
||||||
|
|
Loading…
Reference in New Issue