Merge refcounting of res_odbc
authorTilghman Lesher <tilghman@meg.abyt.es>
Mon, 5 May 2008 23:38:15 +0000 (23:38 +0000)
committerTilghman Lesher <tilghman@meg.abyt.es>
Mon, 5 May 2008 23:38:15 +0000 (23:38 +0000)
git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@115337 65c4cc65-6c06-0410-ace0-fbb531ad65f3

res/res_odbc.c

index 34d905b..701af58 100644 (file)
@@ -47,6 +47,7 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 #include "asterisk/lock.h"
 #include "asterisk/res_odbc.h"
 #include "asterisk/time.h"
+#include "asterisk/astobj2.h"
 
 struct odbc_class
 {
@@ -55,23 +56,52 @@ struct odbc_class
        char dsn[80];
        char *username;
        char *password;
-       char sanitysql[256];
+       char *sanitysql;
        SQLHENV env;
-       unsigned int haspool:1;         /* Boolean - TDS databases need this */
-       unsigned int limit:10;          /* Gives a limit of 1023 maximum */
-       unsigned int count:10;          /* Running count of pooled connections */
-       unsigned int delme:1;                   /* Purge the class */
-       unsigned int backslash_is_escape:1;     /* On this database, the backslash is a native escape sequence */
-       unsigned int idlecheck;                 /* Recheck the connection if it is idle for this long */
-       AST_LIST_HEAD(, odbc_obj) odbc_obj;
+       unsigned int haspool:1;              /* Boolean - TDS databases need this */
+       unsigned int limit:10;               /* Gives a limit of 1023 maximum */
+       unsigned int count:10;               /* Running count of pooled connections */
+       unsigned int delme:1;                /* Purge the class */
+       unsigned int backslash_is_escape:1;  /* On this database, the backslash is a native escape sequence */
+       unsigned int idlecheck;              /* Recheck the connection if it is idle for this long */
+       struct ao2_container *obj_container;
 };
 
-AST_LIST_HEAD_STATIC(odbc_list, odbc_class);
+struct ao2_container *class_container;
 
 static odbc_status odbc_obj_connect(struct odbc_obj *obj);
 static odbc_status odbc_obj_disconnect(struct odbc_obj *obj);
 static int odbc_register_class(struct odbc_class *class, int connect);
 
+static void odbc_class_destructor(void *data)
+{
+       struct odbc_class *class = data;
+       /* Due to refcounts, we can safely assume that any objects with a reference
+        * to us will prevent our destruction, so we don't need to worry about them.
+        */
+       if (class->username)
+               ast_free(class->username);
+       if (class->password)
+               ast_free(class->password);
+       if (class->sanitysql)
+               ast_free(class->sanitysql);
+       ao2_ref(class->obj_container, -1);
+       SQLFreeHandle(SQL_HANDLE_ENV, class->env);
+}
+
+static int null_hash_fn(const void *obj, const int flags)
+{
+       return 0;
+}
+
+static void odbc_obj_destructor(void *data)
+{
+       struct odbc_obj *obj = data;
+       odbc_obj_disconnect(obj);
+       ast_mutex_destroy(&obj->lock);
+       ao2_ref(obj->parent, -1);
+       ast_free(obj);
+}
 
 SQLHSTMT ast_odbc_direct_execute(struct odbc_obj *obj, SQLHSTMT (*exec_cb)(struct odbc_obj *obj, void *data), void *data)
 {
@@ -167,22 +197,6 @@ int ast_odbc_smart_execute(struct odbc_obj *obj, SQLHSTMT stmt)
                                }
                        }
                }
-#if 0
-               /* This is a really bad method of trying to correct a dead connection.  It
-                * only ever really worked with MySQL.  It will not work with any other
-                * database, since most databases prepare their statements on the server,
-                * and if you disconnect, you invalidate the statement handle.  Hence, if
-                * you disconnect, you're going to fail anyway, whether you try to execute
-                * a second time or not.
-                */
-               ast_log(LOG_WARNING, "SQL Execute error %d! Attempting a reconnect...\n", res);
-               ast_mutex_lock(&obj->lock);
-               obj->up = 0;
-               ast_mutex_unlock(&obj->lock);
-               odbc_obj_disconnect(obj);
-               odbc_obj_connect(obj);
-               res = SQLExecute(stmt);
-#endif
        } else
                obj->last_used = ast_tvnow();
        
@@ -296,33 +310,24 @@ static int load_odbc_config(void)
                        }
 
                        if (enabled && !ast_strlen_zero(dsn)) {
-                               new = ast_calloc(1, sizeof(*new));
+                               new = ao2_alloc(sizeof(*new), odbc_class_destructor);
 
                                if (!new) {
                                        res = -1;
                                        break;
                                }
 
-                               if (cat)
-                                       ast_copy_string(new->name, cat, sizeof(new->name));
-                               if (dsn)
-                                       ast_copy_string(new->dsn, dsn, sizeof(new->dsn));
-                               if (username)
-                                       new->username = ast_strdup(username);
-                               if (password)
-                                       new->password = ast_strdup(password);
-                               if (sanitysql)
-                                       ast_copy_string(new->sanitysql, sanitysql, sizeof(new->sanitysql));
-
                                SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &new->env);
                                res = SQLSetEnvAttr(new->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
 
                                if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
                                        ast_log(LOG_WARNING, "res_odbc: Error SetEnv\n");
-                                       SQLFreeHandle(SQL_HANDLE_ENV, new->env);
+                                       ao2_ref(new, -1);
                                        return res;
                                }
 
+                               new->obj_container = ao2_container_alloc(1, null_hash_fn, ao2_match_by_addr);
+
                                if (pooling) {
                                        new->haspool = pooling;
                                        if (limit) {
@@ -336,8 +341,27 @@ static int load_odbc_config(void)
                                new->backslash_is_escape = bse ? 1 : 0;
                                new->idlecheck = idlecheck;
 
+                               if (cat)
+                                       ast_copy_string(new->name, cat, sizeof(new->name));
+                               if (dsn)
+                                       ast_copy_string(new->dsn, dsn, sizeof(new->dsn));
+                               if (username && !(new->username = ast_strdup(username))) {
+                                       ao2_ref(new, -1);
+                                       break;
+                               }
+                               if (password && !(new->password = ast_strdup(password))) {
+                                       ao2_ref(new, -1);
+                                       break;
+                               }
+                               if (sanitysql && !(new->sanitysql = ast_strdup(sanitysql))) {
+                                       ao2_ref(new, -1);
+                                       break;
+                               }
+
                                odbc_register_class(new, connect);
                                ast_log(LOG_NOTICE, "Registered ODBC class '%s' dsn->[%s]\n", cat, dsn);
+                               ao2_ref(new, -1);
+                               new = NULL;
                        }
                }
        }
@@ -347,6 +371,7 @@ static int load_odbc_config(void)
 
 static char *handle_cli_odbc_show(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
 {
+       struct ao2_iterator aoi = ao2_iterator_init(class_container, 0);
        struct odbc_class *class;
        struct odbc_obj *current;
        int length = 0;
@@ -365,44 +390,51 @@ static char *handle_cli_odbc_show(struct ast_cli_entry *e, int cmd, struct ast_c
                if (a->pos != 2)
                        return NULL;
                length = strlen(a->word);
-               AST_LIST_LOCK(&odbc_list);
-               AST_LIST_TRAVERSE(&odbc_list, class, list) {
+               while ((class = ao2_iterator_next(&aoi))) {
                        if (!strncasecmp(a->word, class->name, length) && ++which > a->n) {
                                ret = ast_strdup(class->name);
+                               ao2_ref(class, -1);
                                break;
                        }
+                       ao2_ref(class, -1);
                }
                if (!ret && !strncasecmp(a->word, "all", length) && ++which > a->n) {
                        ret = ast_strdup("all");
                }
-               AST_LIST_UNLOCK(&odbc_list);
                return ret;
        }
 
        ast_cli(a->fd, "\nODBC DSN Settings\n");
-       ast_cli(a->fd, "-----------------\n\n");
-       AST_LIST_LOCK(&odbc_list);
-       AST_LIST_TRAVERSE(&odbc_list, class, list) {
+       ast_cli(a->fd,   "-----------------\n\n");
+       aoi = ao2_iterator_init(class_container, 0);
+       while ((class = ao2_iterator_next(&aoi))) {
                if ((a->argc == 2) || (a->argc == 3 && !strcmp(a->argv[2], "all")) || (!strcmp(a->argv[2], class->name))) {
                        int count = 0;
                        ast_cli(a->fd, "  Name:   %s\n  DSN:    %s\n", class->name, class->dsn);
 
                        if (class->haspool) {
+                               struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, 0);
+
                                ast_cli(a->fd, "  Pooled: Yes\n  Limit:  %d\n  Connections in use: %d\n", class->limit, class->count);
 
-                               AST_LIST_TRAVERSE(&(class->odbc_obj), current, list) {
-                                       ast_cli(a->fd, "    - Connection %d: %s\n", ++count, current->used ? "in use" : current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected");
+                               while ((current = ao2_iterator_next(&aoi2))) {
+                                       ast_cli(a->fd, "    - Connection %d: %s\n", ++count,
+                                               current->used ? "in use" :
+                                               current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected");
+                                       ao2_ref(current, -1);
                                }
                        } else {
                                /* Should only ever be one of these */
-                               AST_LIST_TRAVERSE(&(class->odbc_obj), current, list) {
+                               struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, 0);
+                               while ((current = ao2_iterator_next(&aoi2))) {
                                        ast_cli(a->fd, "  Pooled: No\n  Connected: %s\n", current->up && ast_odbc_sanity_check(current) ? "Yes" : "No");
+                                       ao2_ref(current, -1);
                                }
                        }
                        ast_cli(a->fd, "\n");
                }
+               ao2_ref(class, -1);
        }
-       AST_LIST_UNLOCK(&odbc_list);
 
        return CLI_SUCCESS;
 }
@@ -415,9 +447,8 @@ static int odbc_register_class(struct odbc_class *class, int connect)
 {
        struct odbc_obj *obj;
        if (class) {
-               AST_LIST_LOCK(&odbc_list);
-               AST_LIST_INSERT_HEAD(&odbc_list, class, list);
-               AST_LIST_UNLOCK(&odbc_list);
+               ao2_link(class_container, class);
+               /* I still have a reference in the caller, so a deref is NOT missing here. */
 
                if (connect) {
                        /* Request and release builds a connection */
@@ -438,6 +469,7 @@ void ast_odbc_release_obj(struct odbc_obj *obj)
        /* For pooled connections, this frees the connection to be
         * reused.  For non-pooled connections, it does nothing. */
        obj->used = 0;
+       ao2_ref(obj, -1);
 }
 
 int ast_odbc_backslash_is_escape(struct odbc_obj *obj)
@@ -449,74 +481,77 @@ struct odbc_obj *ast_odbc_request_obj(const char *name, int check)
 {
        struct odbc_obj *obj = NULL;
        struct odbc_class *class;
+       struct ao2_iterator aoi = ao2_iterator_init(class_container, 0);
 
-       AST_LIST_LOCK(&odbc_list);
-       AST_LIST_TRAVERSE(&odbc_list, class, list) {
+       while ((class = ao2_iterator_next(&aoi))) {
                if (!strcmp(class->name, name))
                        break;
+               ao2_ref(class, -1);
        }
-       AST_LIST_UNLOCK(&odbc_list);
 
        if (!class)
                return NULL;
 
-       AST_LIST_LOCK(&class->odbc_obj);
        if (class->haspool) {
                /* Recycle connections before building another */
-               AST_LIST_TRAVERSE(&class->odbc_obj, obj, list) {
+               aoi = ao2_iterator_init(class->obj_container, 0);
+               while ((obj = ao2_iterator_next(&aoi))) {
                        if (! obj->used) {
                                obj->used = 1;
                                break;
                        }
+                       ao2_ref(obj, -1);
                }
 
                if (!obj && (class->count < class->limit)) {
                        class->count++;
-                       obj = ast_calloc(1, sizeof(*obj));
+                       obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor);
                        if (!obj) {
-                               AST_LIST_UNLOCK(&class->odbc_obj);
+                               ao2_ref(class, -1);
                                return NULL;
                        }
                        ast_mutex_init(&obj->lock);
+                       /* obj inherits the outstanding reference to class */
                        obj->parent = class;
                        if (odbc_obj_connect(obj) == ODBC_FAIL) {
                                ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
                                ast_mutex_destroy(&obj->lock);
-                               ast_free(obj);
+                               ao2_ref(obj, -1);
                                obj = NULL;
                                class->count--;
                        } else {
                                obj->used = 1;
-                               AST_LIST_INSERT_TAIL(&class->odbc_obj, obj, list);
+                               ao2_link(class->obj_container, obj);
                        }
                }
        } else {
                /* Non-pooled connection: multiple modules can use the same connection. */
-               AST_LIST_TRAVERSE(&class->odbc_obj, obj, list) {
+               aoi = ao2_iterator_init(class->obj_container, 0);
+               while ((obj = ao2_iterator_next(&aoi))) {
                        /* Non-pooled connection: if there is an entry, return it */
                        break;
                }
 
                if (!obj) {
                        /* No entry: build one */
-                       obj = ast_calloc(1, sizeof(*obj));
+                       obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor);
                        if (!obj) {
-                               AST_LIST_UNLOCK(&class->odbc_obj);
+                               ao2_ref(class, -1);
                                return NULL;
                        }
                        ast_mutex_init(&obj->lock);
+                       /* obj inherits the outstanding reference to class */
                        obj->parent = class;
                        if (odbc_obj_connect(obj) == ODBC_FAIL) {
                                ast_log(LOG_WARNING, "Failed to connect to %s\n", name);
                                ast_mutex_destroy(&obj->lock);
-                               ast_free(obj);
+                               ao2_ref(obj, -1);
                                obj = NULL;
                        } else {
-                               AST_LIST_INSERT_HEAD(&class->odbc_obj, obj, list);
+                               ao2_link(class->obj_container, obj);
                        }
                }
        }
-       AST_LIST_UNLOCK(&class->odbc_obj);
 
        if (obj && check) {
                ast_odbc_sanity_check(obj);
@@ -599,183 +634,30 @@ static odbc_status odbc_obj_connect(struct odbc_obj *obj)
 
 static int reload(void)
 {
-       static char *cfg = "res_odbc.conf";
-       struct ast_config *config;
-       struct ast_variable *v;
-       char *cat;
-       const char *dsn, *username, *password, *sanitysql;
-       int enabled, pooling, limit, bse;
-       unsigned int idlecheck;
-       int connect = 0, res = 0;
-       struct ast_flags config_flags = { CONFIG_FLAG_FILEUNCHANGED };
-
-       struct odbc_class *new, *class;
+       struct odbc_class *class;
        struct odbc_obj *current;
+       struct ao2_iterator aoi = ao2_iterator_init(class_container, 0);
 
        /* First, mark all to be purged */
-       AST_LIST_LOCK(&odbc_list);
-       AST_LIST_TRAVERSE(&odbc_list, class, list) {
+       while ((class = ao2_iterator_next(&aoi))) {
                class->delme = 1;
+               ao2_ref(class, -1);
        }
 
-       config = ast_config_load(cfg, config_flags);
-       if (config != NULL && config != CONFIG_STATUS_FILEUNCHANGED) {
-               for (cat = ast_category_browse(config, NULL); cat; cat=ast_category_browse(config, cat)) {
-                       if (!strcasecmp(cat, "ENV")) {
-                               for (v = ast_variable_browse(config, cat); v; v = v->next) {
-                                       setenv(v->name, v->value, 1);
-                                       ast_log(LOG_NOTICE, "Adding ENV var: %s=%s\n", v->name, v->value);
-                               }
-                       } else {
-                               char *freeme = NULL;
-                               /* Reset all to defaults for each class of odbc connections */
-                               dsn = username = password = sanitysql = NULL;
-                               enabled = 1;
-                               connect = idlecheck = 0;
-                               pooling = 0;
-                               limit = 0;
-                               bse = 1;
-                               for (v = ast_variable_browse(config, cat); v; v = v->next) {
-                                       if (!strcasecmp(v->name, "pooling")) {
-                                               if (ast_true(v->value))
-                                                       pooling = 1;
-                                       } else if (!strncasecmp(v->name, "share", 5)) {
-                                               /* "shareconnections" is a little clearer in meaning than "pooling" */
-                                               if (ast_false(v->value))
-                                                       pooling = 1;
-                                       } else if (!strcasecmp(v->name, "limit")) {
-                                               sscanf(v->value, "%d", &limit);
-                                               if (ast_true(v->value) && !limit) {
-                                                       ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'.  Setting limit to 1023 for ODBC class '%s'.\n", v->value, cat);
-                                                       limit = 1023;
-                                               } else if (ast_false(v->value)) {
-                                                       ast_log(LOG_WARNING, "Limit should be a number, not a boolean: '%s'.  Disabling ODBC class '%s'.\n", v->value, cat);
-                                                       enabled = 0;
-                                                       break;
-                                               }
-                                       } else if (!strcasecmp(v->name, "idlecheck")) {
-                                               sscanf(v->value, "%ud", &idlecheck);
-                                       } else if (!strcasecmp(v->name, "enabled")) {
-                                               enabled = ast_true(v->value);
-                                       } else if (!strcasecmp(v->name, "pre-connect")) {
-                                               connect = ast_true(v->value);
-                                       } else if (!strcasecmp(v->name, "dsn")) {
-                                               dsn = v->value;
-                                       } else if (!strcasecmp(v->name, "username")) {
-                                               username = v->value;
-                                       } else if (!strcasecmp(v->name, "password")) {
-                                               password = v->value;
-                                       } else if (!strcasecmp(v->name, "sanitysql")) {
-                                               sanitysql = v->value;
-                                       } else if (!strcasecmp(v->name, "backslash_is_escape")) {
-                                               bse = ast_true(v->value);
-                                       }
-                               }
-
-                               if (enabled && !ast_strlen_zero(dsn)) {
-                                       /* First, check the list to see if it already exists */
-                                       AST_LIST_TRAVERSE(&odbc_list, class, list) {
-                                               if (!strcmp(class->name, cat)) {
-                                                       class->delme = 0;
-                                                       break;
-                                               }
-                                       }
-
-                                       if (class) {
-                                               new = class;
-                                       } else {
-                                               new = ast_calloc(1, sizeof(*new));
-                                       }
-
-                                       if (!new) {
-                                               res = -1;
-                                               break;
-                                       }
-
-                                       if (cat)
-                                               ast_copy_string(new->name, cat, sizeof(new->name));
-                                       if (dsn)
-                                               ast_copy_string(new->dsn, dsn, sizeof(new->dsn));
-
-                                       /* Safely replace username */
-                                       if (class && class->username)
-                                               freeme = class->username;
-                                       if (username)
-                                               new->username = ast_strdup(username);
-                                       if (freeme) {
-                                               ast_free(freeme);
-                                               freeme = NULL;
-                                       }
-
-                                       /* Safely replace password */
-                                       if (class && class->password)
-                                                freeme = class->password;
-                                       if (password)
-                                               new->password = ast_strdup(password);
-                                       if (freeme) {
-                                               ast_free(freeme);
-                                               freeme = NULL;
-                                       }
-
-                                       if (sanitysql)
-                                               ast_copy_string(new->sanitysql, sanitysql, sizeof(new->sanitysql));
-
-                                       if (!class) {
-                                               SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &new->env);
-                                               res = SQLSetEnvAttr(new->env, SQL_ATTR_ODBC_VERSION, (void *) SQL_OV_ODBC3, 0);
-
-                                               if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) {
-                                                       ast_log(LOG_WARNING, "res_odbc: Error SetEnv\n");
-                                                       SQLFreeHandle(SQL_HANDLE_ENV, new->env);
-                                                       AST_LIST_UNLOCK(&odbc_list);
-                                                       return res;
-                                               }
-                                       }
-
-                                       if (pooling) {
-                                               new->haspool = pooling;
-                                               if (limit) {
-                                                       new->limit = limit;
-                                               } else {
-                                                       ast_log(LOG_WARNING, "Pooling without also setting a limit is pointless.  Changing limit from 0 to 5.\n");
-                                                       new->limit = 5;
-                                               }
-                                       }
-
-                                       new->backslash_is_escape = bse;
-                                       new->idlecheck = idlecheck;
-
-                                       if (class) {
-                                               ast_log(LOG_NOTICE, "Refreshing ODBC class '%s' dsn->[%s]\n", cat, dsn);
-                                       } else {
-                                               odbc_register_class(new, connect);
-                                               ast_log(LOG_NOTICE, "Registered ODBC class '%s' dsn->[%s]\n", cat, dsn);
-                                       }
-                               }
-                       }
-               }
-               ast_config_destroy(config);
-       }
+       load_odbc_config();
 
-       /* Purge classes that we know can go away (pooled with 0, only) */
-       AST_LIST_TRAVERSE_SAFE_BEGIN(&odbc_list, class, list) {
-               if (class->delme && class->haspool && class->count == 0) {
-                       while ((current = AST_LIST_REMOVE_HEAD(&class->odbc_obj, list))) {
-                               odbc_obj_disconnect(current);
-                               ast_mutex_destroy(&current->lock);
-                               ast_free(current);
+       /* Purge remaining classes */
+       aoi = ao2_iterator_init(class_container, OBJ_UNLINK);
+       while ((class = ao2_iterator_next(&aoi))) {
+               if (class->delme) {
+                       struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, OBJ_UNLINK);
+                       while ((current = ao2_iterator_next(&aoi2))) {
+                               ao2_ref(current, -2);
                        }
-
-                       AST_LIST_REMOVE_CURRENT(list);
-                       if (class->username)
-                               ast_free(class->username);
-                       if (class->password)
-                               ast_free(class->password);
-                       ast_free(class);
+                       ao2_ref(class, -1);
                }
+               ao2_ref(class, -1);
        }
-       AST_LIST_TRAVERSE_SAFE_END;
-       AST_LIST_UNLOCK(&odbc_list);
 
        return 0;
 }
@@ -788,6 +670,8 @@ static int unload_module(void)
 
 static int load_module(void)
 {
+       if (!(class_container = ao2_container_alloc(1, null_hash_fn, ao2_match_by_addr)))
+               return AST_MODULE_LOAD_DECLINE;
        if (load_odbc_config() == -1)
                return AST_MODULE_LOAD_DECLINE;
        ast_cli_register_multiple(cli_odbc, sizeof(cli_odbc) / sizeof(struct ast_cli_entry));