From 6319eab542dcec0bab245a073e7a41c905446ffc Mon Sep 17 00:00:00 2001 From: tilghman Date: Mon, 5 May 2008 23:38:15 +0000 Subject: Merge refcounting of res_odbc git-svn-id: http://svn.digium.com/svn/asterisk/trunk@115337 f38db490-d61c-443f-a65b-d21fe96a405b --- res/res_odbc.c | 350 +++++++++++++++++++-------------------------------------- 1 file changed, 117 insertions(+), 233 deletions(-) (limited to 'res') diff --git a/res/res_odbc.c b/res/res_odbc.c index 34d905b55..701af585d 100644 --- a/res/res_odbc.c +++ b/res/res_odbc.c @@ -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(¤t->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)); -- cgit v1.2.3