Index: res/res_odbc.c =================================================================== --- res/res_odbc.c (revision 196272) +++ res/res_odbc.c (working copy) @@ -48,6 +48,7 @@ #include "asterisk/lock.h" #include "asterisk/res_odbc.h" #include "asterisk/time.h" +#define REF_DEBUG #include "asterisk/astobj2.h" #include "asterisk/app.h" #include "asterisk/strings.h" @@ -141,7 +142,7 @@ static odbc_status odbc_obj_disconnect(struct odbc_obj *obj); static int odbc_register_class(struct odbc_class *class, int connect); static void odbc_txn_free(void *data); -static void odbc_release_obj2(struct odbc_obj *obj, struct odbc_txn_frame *tx); +static void odbc_release_obj2(struct odbc_obj *obj, struct odbc_txn_frame *tx, char *reason); AST_THREADSTORAGE(errors_buf); @@ -265,6 +266,7 @@ strcpy(txn->name, name); /* SAFE */ txn->obj = obj; + ao2_t_ref(txn->obj, 1, "Storing transaction handle on channel datastore"); txn->isolation = obj->parent->isolation; txn->forcecommit = obj->parent->forcecommit; txn->owner = chan; @@ -314,7 +316,7 @@ /* Prevent recursion during destruction */ tx->obj->txf = NULL; tx->obj = NULL; - odbc_release_obj2(obj, tx); + odbc_release_obj2(obj, tx, "releasing handle from transaction"); } ast_free(tx); return NULL; @@ -382,7 +384,7 @@ if (class->sanitysql) { ast_free(class->sanitysql); } - ao2_ref(class->obj_container, -1); + ao2_t_ref(class->obj_container, -1, "Destroying class object container"); SQLFreeHandle(SQL_HANDLE_ENV, class->env); } @@ -398,7 +400,7 @@ obj->parent = NULL; odbc_obj_disconnect(obj); ast_mutex_destroy(&obj->lock); - ao2_ref(class, -1); + ao2_t_ref(class, -1, "Removing reference to class from within object destructor"); } static void destroy_table_cache(struct odbc_cache_tables *table) { @@ -807,7 +809,7 @@ } if (enabled && !ast_strlen_zero(dsn)) { - new = ao2_alloc(sizeof(*new), odbc_class_destructor); + new = ao2_t_alloc(sizeof(*new), odbc_class_destructor, "Creating new class"); if (!new) { res = -1; @@ -819,11 +821,11 @@ if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { ast_log(LOG_WARNING, "res_odbc: Error SetEnv\n"); - ao2_ref(new, -1); + ao2_t_ref(new, -1, "Failed to setup ODBC environment, deleting class"); return res; } - new->obj_container = ao2_container_alloc(1, null_hash_fn, ao2_match_by_addr); + new->obj_container = ao2_t_container_alloc(1, null_hash_fn, ao2_match_by_addr, "Creating object container within class"); if (pooling) { new->haspool = pooling; @@ -845,21 +847,21 @@ if (dsn) ast_copy_string(new->dsn, dsn, sizeof(new->dsn)); if (username && !(new->username = ast_strdup(username))) { - ao2_ref(new, -1); + ao2_t_ref(new, -1, "Memory allocation error, deleting new class"); break; } if (password && !(new->password = ast_strdup(password))) { - ao2_ref(new, -1); + ao2_t_ref(new, -1, "Memory allocation error, deleting new class"); break; } if (sanitysql && !(new->sanitysql = ast_strdup(sanitysql))) { - ao2_ref(new, -1); + ao2_t_ref(new, -1, "Memory allocation error, deleting new class"); break; } odbc_register_class(new, preconnect); ast_log(LOG_NOTICE, "Registered ODBC class '%s' dsn->[%s]\n", cat, dsn); - ao2_ref(new, -1); + ao2_t_ref(new, -1, "Done creating class"); new = NULL; } } @@ -889,11 +891,11 @@ if (a->pos != 2) return NULL; length = strlen(a->word); - while ((class = ao2_iterator_next(&aoi))) { + while ((class = ao2_t_iterator_next(&aoi, "Iterator over classes"))) { if (!strncasecmp(a->word, class->name, length) && ++which > a->n) { ret = ast_strdup(class->name); } - ao2_ref(class, -1); + ao2_t_ref(class, -1, "Done with class iterator"); if (ret) { break; } @@ -907,7 +909,7 @@ ast_cli(a->fd, "\nODBC DSN Settings\n"); ast_cli(a->fd, "-----------------\n\n"); aoi = ao2_iterator_init(class_container, 0); - while ((class = ao2_iterator_next(&aoi))) { + while ((class = ao2_t_iterator_next(&aoi, "Iterator over classes"))) { 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); @@ -917,7 +919,7 @@ ast_cli(a->fd, " Pooled: Yes\n Limit: %d\n Connections in use: %d\n", class->limit, class->count); - while ((current = ao2_iterator_next(&aoi2))) { + while ((current = ao2_t_iterator_next(&aoi2, "Iterator over objects within pooled class"))) { ast_mutex_lock(¤t->lock); #ifdef DEBUG_THREADS ast_cli(a->fd, " - Connection %d: %s (%s:%d %s)\n", ++count, @@ -930,20 +932,20 @@ current->up && ast_odbc_sanity_check(current) ? "connected" : "disconnected"); #endif ast_mutex_unlock(¤t->lock); - ao2_ref(current, -1); + ao2_t_ref(current, -1, "Done with object iterator"); } } else { /* Should only ever be one of these (unless there are transactions) */ struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, 0); - while ((current = ao2_iterator_next(&aoi2))) { + while ((current = ao2_t_iterator_next(&aoi2, "Iterator over objects within non-pooled class"))) { ast_cli(a->fd, " Pooled: No\n Connected: %s\n", current->used ? "In use" : current->up && ast_odbc_sanity_check(current) ? "Yes" : "No"); - ao2_ref(current, -1); + ao2_t_ref(current, -1, "Done with object iterator"); } } ast_cli(a->fd, "\n"); } - ao2_ref(class, -1); + ao2_t_ref(class, -1, "Done with class iterator"); } return CLI_SUCCESS; @@ -957,7 +959,7 @@ { struct odbc_obj *obj; if (class) { - ao2_link(class_container, class); + ao2_t_link(class_container, class, "Register class into class container"); /* I still have a reference in the caller, so a deref is NOT missing here. */ if (preconnect) { @@ -975,7 +977,7 @@ } } -static void odbc_release_obj2(struct odbc_obj *obj, struct odbc_txn_frame *tx) +static void odbc_release_obj2(struct odbc_obj *obj, struct odbc_txn_frame *tx, char *reason) { SQLINTEGER nativeerror=0, numfields=0; SQLSMALLINT diagbytes=0, i; @@ -1031,13 +1033,28 @@ obj->txf->obj = NULL; obj->txf = release_transaction(obj->txf); } - ao2_ref(obj, -1); + + /* Safeguard container */ + if (ao2_t_ref(obj, 0, "Checking refcounts") == 1) { + ao2_t_ref(obj, 1, "Safety increment"); /* Now 2 */ + ao2_t_unlink(obj->parent->obj_container, obj, "Attempting removal of object from class"); + if (ao2_ref(obj, 0) == 1) { + ast_log(LOG_ERROR, "Reference counts incorrect! Unlinking database object from container as a safeguard.\n"); + ast_backtrace(); + ao2_t_ref(obj, -1, "Safety decrement (and now destroyed)"); /* Destroyed */ + return; + } + /* This else case could happen naturally if the object is used past a reload */ + ao2_t_ref(obj, -1, "Temporary decrement"); /* Now 1 */ + } + + ao2_t_ref(obj, -1, reason); } void ast_odbc_release_obj(struct odbc_obj *obj) { struct odbc_txn_frame *tx = find_transaction(NULL, obj, NULL, 0); - odbc_release_obj2(obj, tx); + odbc_release_obj2(obj, tx, "API call, releasing object"); } int ast_odbc_backslash_is_escape(struct odbc_obj *obj) @@ -1158,7 +1175,7 @@ SQLSMALLINT diagbytes=0, i; unsigned char state[10], diagnostic[256]; - if (!(class = ao2_callback(class_container, 0, aoro2_class_cb, (char *) name))) { + if (!(class = ao2_t_callback(class_container, 0, aoro2_class_cb, (char *) name, "Find class"))) { return NULL; } @@ -1166,16 +1183,16 @@ if (class->haspool) { /* Recycle connections before building another */ - obj = ao2_callback(class->obj_container, 0, aoro2_obj_cb, EOR_TX); + obj = ao2_t_callback(class->obj_container, 0, aoro2_obj_cb, EOR_TX, "Find object within class"); if (obj) { ast_assert(ao2_ref(obj, 0) > 1); } if (!obj && (class->count < class->limit)) { - obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor); + obj = ao2_t_alloc(sizeof(*obj), odbc_obj_destructor, "Creating new object instance within class"); if (!obj) { - ao2_ref(class, -1); + ao2_t_ref(class, -1, "Object allocation failed, decrement class ref"); return NULL; } ast_assert(ao2_ref(obj, 0) == 1); @@ -1185,17 +1202,17 @@ class = NULL; if (odbc_obj_connect(obj) == ODBC_FAIL) { ast_log(LOG_WARNING, "Failed to connect to %s\n", name); - ao2_ref(obj, -1); + ao2_t_ref(obj, -1, "Connection failed, deleting object"); ast_assert(ao2_ref(class, 0) > 0); obj = NULL; } else { obj->used = 1; - ao2_link(obj->parent->obj_container, obj); + ao2_t_link(obj->parent->obj_container, obj, "Adding object to class"); ast_atomic_fetchadd_int(&obj->parent->count, +1); } } else { /* Object is not constructed, so delete outstanding reference to class. */ - ao2_ref(class, -1); + ao2_t_ref(class, -1, "Object recycled, class reference decremented"); class = NULL; } @@ -1215,10 +1232,10 @@ } } else if (ast_test_flag(&flags, RES_ODBC_INDEPENDENT_CONNECTION)) { /* Non-pooled connections -- but must use a separate connection handle */ - if (!(obj = ao2_callback(class->obj_container, 0, aoro2_obj_cb, USE_TX))) { - obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor); + if (!(obj = ao2_t_callback(class->obj_container, 0, aoro2_obj_cb, USE_TX, "Find transactional object within class"))) { + obj = ao2_t_alloc(sizeof(*obj), odbc_obj_destructor, "Creating new transactional object instance within class"); if (!obj) { - ao2_ref(class, -1); + ao2_t_ref(class, -1, "Object allocation failed, decrementing class"); return NULL; } ast_mutex_init(&obj->lock); @@ -1227,11 +1244,11 @@ class = NULL; if (odbc_obj_connect(obj) == ODBC_FAIL) { ast_log(LOG_WARNING, "Failed to connect to %s\n", name); - ao2_ref(obj, -1); + ao2_t_ref(obj, -1, "Connection failed, deleting object"); obj = NULL; } else { obj->used = 1; - ao2_link(obj->parent->obj_container, obj); + ao2_t_link(obj->parent->obj_container, obj, "Adding transactional object to class"); ast_atomic_fetchadd_int(&obj->parent->count, +1); } } @@ -1249,16 +1266,16 @@ } } else { /* Non-pooled connection: multiple modules can use the same connection. */ - if ((obj = ao2_callback(class->obj_container, 0, aoro2_obj_cb, NO_TX))) { + if ((obj = ao2_t_callback(class->obj_container, 0, aoro2_obj_cb, NO_TX, "Finding shared connection within class"))) { /* Object is not constructed, so delete outstanding reference to class. */ ast_assert(ao2_ref(class, 0) > 1); - ao2_ref(class, -1); + ao2_t_ref(class, -1, "Object recycled, decrementing class"); class = NULL; } else { /* No entry: build one */ - if (!(obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor))) { + if (!(obj = ao2_t_alloc(sizeof(*obj), odbc_obj_destructor, "Creating new shared object instance within class"))) { ast_assert(ao2_ref(class, 0) > 1); - ao2_ref(class, -1); + ao2_t_ref(class, -1, "Object allocation failed, decrementing class"); return NULL; } ast_mutex_init(&obj->lock); @@ -1267,10 +1284,10 @@ class = NULL; if (odbc_obj_connect(obj) == ODBC_FAIL) { ast_log(LOG_WARNING, "Failed to connect to %s\n", name); - ao2_ref(obj, -1); + ao2_t_ref(obj, -1, "Connection failed, deleting object"); obj = NULL; } else { - ao2_link(obj->parent->obj_container, obj); + ao2_t_link(obj->parent->obj_container, obj, "Adding shared object to class"); ast_assert(ao2_ref(obj, 0) > 1); } } @@ -1605,9 +1622,9 @@ struct ao2_iterator aoi = ao2_iterator_init(class_container, 0); /* First, mark all to be purged */ - while ((class = ao2_iterator_next(&aoi))) { + while ((class = ao2_t_iterator_next(&aoi, "Marking all existing classes to be deleted"))) { class->delme = 1; - ao2_ref(class, -1); + ao2_t_ref(class, -1, "Done with iterator over classes"); } load_odbc_config(); @@ -1637,25 +1654,25 @@ * removes the final C-ref, the class will be destroyed. */ aoi = ao2_iterator_init(class_container, 0); - while ((class = ao2_iterator_next(&aoi))) { /* C-ref++ (by iterator) */ + while ((class = ao2_t_iterator_next(&aoi, "Iterate classes within class container"))) { /* C-ref++ (by iterator) */ if (class->delme) { struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, 0); - while ((current = ao2_iterator_next(&aoi2))) { /* O-ref++ (by iterator) */ - ao2_unlink(class->obj_container, current); /* unlink O-ref from class (reference handled implicitly) */ - ao2_ref(current, -1); /* O-ref-- (by iterator) */ + while ((current = ao2_t_iterator_next(&aoi2, "Iterate objects within class"))) { /* O-ref++ (by iterator) */ + ao2_t_unlink(class->obj_container, current, "Delete object within class"); /* (reference handled implicitly) */ + ao2_t_ref(current, -1, "Deleting object iterator ref"); /* O-ref-- (by iterator) */ /* At this point, either * a) there's an outstanding O-ref, or * b) the object has already been destroyed. */ } - ao2_unlink(class_container, class); /* unlink C-ref from container (reference handled implicitly) */ + ao2_t_unlink(class_container, class, "Unlink class from class container"); /* At this point, either * a) there's an outstanding O-ref, which holds an outstanding C-ref, or * b) the last remaining C-ref is held by the iterator, which will be * destroyed in the next step. */ } - ao2_ref(class, -1); /* C-ref-- (by iterator) */ + ao2_t_ref(class, -1, "Deleting class iterator ref"); /* C-ref-- (by iterator) */ } /* Empty the cache; it will get rebuilt the next time the tables are needed. */