diff options
-rw-r--r-- | include/asterisk/astobj2.h | 127 | ||||
-rw-r--r-- | main/astobj2.c | 73 | ||||
-rw-r--r-- | tests/test_astobj2.c | 137 |
3 files changed, 336 insertions, 1 deletions
diff --git a/include/asterisk/astobj2.h b/include/asterisk/astobj2.h index b6bea649e..4b174b05d 100644 --- a/include/asterisk/astobj2.h +++ b/include/asterisk/astobj2.h @@ -564,6 +564,133 @@ int __ao2_trylock(void *a, enum ao2_lock_req lock_how, const char *file, const c */ void *ao2_object_get_lockaddr(void *obj); + +/*! Global ao2 array container base structure. */ +struct ao2_global_obj { + /*! Access lock to the global ao2 array container. */ + ast_rwlock_t lock; + /*! Number of elements in the global ao2 array container. */ + unsigned int num_elements; + /*! Global ao2 array container array. */ + void *obj[0]; +}; + +/*! + * \brief Define a structure to be used to hold a global array of ao2 objects, statically initialized. + * \since 11.0 + * + * \param name This will be the name of the defined structure. + * \param num_objects Number of ao2 objects to contain. + * + * \details + * This macro creates a structure definition that can be used to + * hold an array of ao2 objects accessible using an API. The + * structure is allocated and initialized to be empty. + * + * Example usage: + * \code + * static AO2_GLOBAL_OBJ_STATIC(global_cfg, 10); + * \endcode + * + * This would define \c struct \c global_cfg, intended to hold + * an array of ao2 objects accessible using an API. + */ +#ifndef HAVE_PTHREAD_RWLOCK_INITIALIZER +#define AO2_GLOBAL_OBJ_STATIC(name, num_objects) \ + struct name { \ + struct ao2_global_obj global; \ + void *objs[num_objects]; \ + } name; \ + static void __attribute__((constructor)) __init_##name(void) \ + { \ + unsigned int idx = (num_objects); \ + ast_rwlock_init(&name.global.lock); \ + name.global.num_elements = idx; \ + while (idx--) { \ + name.global.obj[idx] = NULL; \ + } \ + } \ + static void __attribute__((destructor)) __fini_##name(void) \ + { \ + unsigned int idx = (num_objects); \ + while (idx--) { \ + if (name.global.obj[idx]) { \ + ao2_ref(name.global.obj[idx], -1); \ + name.global.obj[idx] = NULL; \ + } \ + } \ + ast_rwlock_destroy(&name.global.lock); \ + } \ + struct __dummy_##name +#else +#define AO2_GLOBAL_OBJ_STATIC(name, num_objects) \ + struct name { \ + struct ao2_global_obj global; \ + void *objs[num_objects]; \ + } name = { \ + .global.lock = AST_RWLOCK_INIT_VALUE, \ + .global.num_elements = (num_objects), \ + } +#endif + +/*! + * \brief Release all global ao2 objects in the global array. + * \since 11.0 + * + * \param array Global ao2 object array container. + * \param tag used for debugging + * + * \return Nothing + */ +#define ao2_t_global_obj_release(array, tag) \ + __ao2_global_obj_release(&array.global, (tag), __FILE__, __LINE__, __PRETTY_FUNCTION__, #array) +#define ao2_global_obj_release(array) \ + __ao2_global_obj_release(&array.global, "", __FILE__, __LINE__, __PRETTY_FUNCTION__, #array) + +void __ao2_global_obj_release(struct ao2_global_obj *array, const char *tag, const char *file, int line, const char *func, const char *name); + +/*! + * \brief Replace a global ao2 object in the global array. + * \since 11.0 + * + * \param array Global ao2 object array container. + * \param idx Index to replace in the array. + * \param obj Object to put into the array. Can be NULL. + * \param tag used for debugging + * + * \note This function automatically increases the reference + * count to account for the reference that the global array now + * holds to the object. + * + * \retval Reference to previous global ao2 object stored at the index. + * \retval NULL if no object available. + */ +#define ao2_t_global_obj_replace(array, idx, obj, tag) \ + __ao2_global_obj_replace(&array.global, (idx), (obj), (tag), __FILE__, __LINE__, __PRETTY_FUNCTION__, #array) +#define ao2_global_obj_replace(array, idx, obj) \ + __ao2_global_obj_replace(&array.global, (idx), (obj), "", __FILE__, __LINE__, __PRETTY_FUNCTION__, #array) + +void *__ao2_global_obj_replace(struct ao2_global_obj *array, unsigned int idx, void *obj, const char *tag, const char *file, int line, const char *func, const char *name); + +/*! + * \brief Get a reference to the object stored in the ao2 global array. + * \since 11.0 + * + * \param array Global ao2 object array container. + * \param idx Index to get an object reference in the array. + * \param tag used for debugging + * + * \retval Reference to current global ao2 object stored at the index. + * \retval NULL if no object available. + */ +#define ao2_t_global_obj_ref(array, idx, tag) \ + __ao2_global_obj_ref(&array.global, (idx), (tag), __FILE__, __LINE__, __PRETTY_FUNCTION__, #array) +#define ao2_global_obj_ref(array, idx) \ + __ao2_global_obj_ref(&array.global, (idx), "", __FILE__, __LINE__, __PRETTY_FUNCTION__, #array) + +void *__ao2_global_obj_ref(struct ao2_global_obj *array, unsigned int idx, const char *tag, const char *file, int line, const char *func, const char *name); + + /*! \page AstObj2_Containers AstObj2 Containers diff --git a/main/astobj2.c b/main/astobj2.c index 32758cfd0..237d0ca85 100644 --- a/main/astobj2.c +++ b/main/astobj2.c @@ -639,6 +639,79 @@ void *__ao2_alloc(size_t data_size, ao2_destructor_fn destructor_fn, unsigned in } +void __ao2_global_obj_release(struct ao2_global_obj *array, const char *tag, const char *file, int line, const char *func, const char *name) +{ + unsigned int idx; + + if (!array) { + /* For sanity */ + return; + } + if (__ast_rwlock_wrlock(file, line, func, &array->lock, name)) { + /* Could not get the write lock. */ + return; + } + + /* Release all contained ao2 objects. */ + idx = array->num_elements; + while (idx--) { + if (array->obj[idx]) { + __ao2_ref_debug(array->obj[idx], -1, tag, file, line, func); + array->obj[idx] = NULL; + } + } + + __ast_rwlock_unlock(file, line, func, &array->lock, name); +} + +void *__ao2_global_obj_replace(struct ao2_global_obj *array, unsigned int idx, void *obj, const char *tag, const char *file, int line, const char *func, const char *name) +{ + void *obj_old; + + if (!array || array->num_elements <= idx) { + /* For sanity */ + return NULL; + } + if (__ast_rwlock_wrlock(file, line, func, &array->lock, name)) { + /* Could not get the write lock. */ + return NULL; + } + + if (obj) { + __ao2_ref_debug(obj, +1, tag, file, line, func); + } + obj_old = array->obj[idx]; + array->obj[idx] = obj; + + __ast_rwlock_unlock(file, line, func, &array->lock, name); + + return obj_old; +} + +void *__ao2_global_obj_ref(struct ao2_global_obj *array, unsigned int idx, const char *tag, const char *file, int line, const char *func, const char *name) +{ + void *obj; + + if (!array || array->num_elements <= idx) { + /* For sanity */ + return NULL; + } + if (__ast_rwlock_rdlock(file, line, func, &array->lock, name)) { + /* Could not get the read lock. */ + return NULL; + } + + obj = array->obj[idx]; + if (obj) { + __ao2_ref_debug(obj, +1, tag, file, line, func); + } + + __ast_rwlock_unlock(file, line, func, &array->lock, name); + + return obj; +} + + /* internal callback to destroy a container. */ static void container_destruct(void *c); diff --git a/tests/test_astobj2.c b/tests/test_astobj2.c index 05b91324f..9dcff34ed 100644 --- a/tests/test_astobj2.c +++ b/tests/test_astobj2.c @@ -335,7 +335,7 @@ static int astobj2_test_helper(int use_hash, int use_cmp, unsigned int lim, stru ao2_iterator_destroy(mult_it); } - /* Is the container count what we expect after all the finds and unlinks?*/ + /* Is the container count what we expect after all the finds and unlinks? */ if (ao2_container_count(c1) != lim) { ast_test_status_update(test, "container count does not match what is expected after ao2_find tests.\n"); res = AST_TEST_FAIL; @@ -567,10 +567,144 @@ cleanup: return res; } +static AO2_GLOBAL_OBJ_STATIC(astobj2_array, 2); + +AST_TEST_DEFINE(astobj2_test_3) +{ + int res = AST_TEST_PASS; + int destructor_count = 0; + int num_objects = 0; + struct test_obj *obj = NULL; + struct test_obj *obj2 = NULL; + struct test_obj *obj3 = NULL; + + switch (cmd) { + case TEST_INIT: + info->name = "astobj2_test3"; + info->category = "/main/astobj2/"; + info->summary = "Test global ao2 array container"; + info->description = + "This test is to see if the global ao2 array container works as intended."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + /* Put an object in index 0 */ + obj = ao2_alloc(sizeof(struct test_obj), test_obj_destructor); + if (!obj) { + ast_test_status_update(test, "ao2_alloc failed.\n"); + res = AST_TEST_FAIL; + goto cleanup; + } + obj->destructor_count = &destructor_count; + obj->i = ++num_objects; + obj2 = ao2_t_global_obj_replace(astobj2_array, 0, obj, "Save object in index 0"); + if (obj2) { + ast_test_status_update(test, "Returned object not expected.\n"); + res = AST_TEST_FAIL; + goto cleanup; + } + /* Save object for next check. */ + obj3 = obj; + + /* Replace an object in index 0 */ + obj = ao2_alloc(sizeof(struct test_obj), test_obj_destructor); + if (!obj) { + ast_test_status_update(test, "ao2_alloc failed.\n"); + res = AST_TEST_FAIL; + goto cleanup; + } + obj->destructor_count = &destructor_count; + obj->i = ++num_objects; + obj2 = ao2_t_global_obj_replace(astobj2_array, 0, obj, "Replace object in index 0"); + if (!obj2) { + ast_test_status_update(test, "Expected an object.\n"); + res = AST_TEST_FAIL; + goto cleanup; + } + if (obj2 != obj3) { + ast_test_status_update(test, "Replaced object not expected object.\n"); + res = AST_TEST_FAIL; + goto cleanup; + } + ao2_ref(obj3, -1); + obj3 = NULL; + ao2_ref(obj2, -1); + obj2 = NULL; + ao2_ref(obj, -1); + + /* Put an object in index 1 */ + obj = ao2_alloc(sizeof(struct test_obj), test_obj_destructor); + if (!obj) { + ast_test_status_update(test, "ao2_alloc failed.\n"); + res = AST_TEST_FAIL; + goto cleanup; + } + obj->destructor_count = &destructor_count; + obj->i = ++num_objects; + obj2 = ao2_t_global_obj_replace(astobj2_array, 1, obj, "Save object in index 1"); + if (obj2) { + ao2_ref(obj2, -1); + ast_test_status_update(test, "Returned object not expected.\n"); + res = AST_TEST_FAIL; + goto cleanup; + } + /* Save object for next check. */ + obj3 = obj; + + /* Get a reference to the object in index 1. */ + obj = ao2_t_global_obj_ref(astobj2_array, 1, "Get reference of index 1 object"); + if (!obj) { + ast_test_status_update(test, "Expected an object.\n"); + res = AST_TEST_FAIL; + goto cleanup; + } + if (obj != obj3) { + ast_test_status_update(test, "Returned object not expected.\n"); + res = AST_TEST_FAIL; + goto cleanup; + } + ao2_ref(obj3, -1); + obj3 = NULL; + ao2_ref(obj, -1); + obj = NULL; + + /* Release all objects in the global array. */ + ao2_t_global_obj_release(astobj2_array, "Check release all objects"); + destructor_count += num_objects; + if (0 < destructor_count) { + ast_test_status_update(test, + "all destructors were not called, destructor count is %d\n", + destructor_count); + res = AST_TEST_FAIL; + } else if (destructor_count < 0) { + ast_test_status_update(test, + "Destructor was called too many times, destructor count is %d\n", + destructor_count); + res = AST_TEST_FAIL; + } + +cleanup: + if (obj) { + ao2_t_ref(obj, -1, "Test cleanup external object 1"); + } + if (obj2) { + ao2_t_ref(obj2, -1, "Test cleanup external object 2"); + } + if (obj3) { + ao2_t_ref(obj3, -1, "Test cleanup external object 3"); + } + ao2_t_global_obj_release(astobj2_array, "Test cleanup array"); + + return res; +} + static int unload_module(void) { AST_TEST_UNREGISTER(astobj2_test_1); AST_TEST_UNREGISTER(astobj2_test_2); + AST_TEST_UNREGISTER(astobj2_test_3); return 0; } @@ -578,6 +712,7 @@ static int load_module(void) { AST_TEST_REGISTER(astobj2_test_1); AST_TEST_REGISTER(astobj2_test_2); + AST_TEST_REGISTER(astobj2_test_3); return AST_MODULE_LOAD_SUCCESS; } |