Add global ao2 array container.
authorRichard Mudgett <rmudgett@digium.com>
Tue, 27 Mar 2012 17:13:32 +0000 (17:13 +0000)
committerRichard Mudgett <rmudgett@digium.com>
Tue, 27 Mar 2012 17:13:32 +0000 (17:13 +0000)
Global ao2 objects must always exist after initialization because there is
no access control to obtain another reference to the global object.

It is expected that module configuration could use these new API calls to
replace an active configuration parameter object with an updated
configuration parameter object.

With these new API calls, the global object could be replaced, removed, or
referenced without the risk of someone using a stale global object
pointer.

Review: https://reviewboard.asterisk.org/r/1824/

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@360627 65c4cc65-6c06-0410-ace0-fbb531ad65f3

include/asterisk/astobj2.h
main/astobj2.c
tests/test_astobj2.c

index b6bea64..4b174b0 100644 (file)
@@ -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
 
index 32758cf..237d0ca 100644 (file)
@@ -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);
 
index 05b9132..9dcff34 100644 (file)
@@ -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;
 }