summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/asterisk/astobj2.h127
-rw-r--r--main/astobj2.c73
-rw-r--r--tests/test_astobj2.c137
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;
}