From 6bb04df2e6f7377453ef49c4263bf1ab27a542bb Mon Sep 17 00:00:00 2001 From: tilghman Date: Fri, 23 Jul 2010 16:19:21 +0000 Subject: [PATCH] Merge the realtime failover branch git-svn-id: http://svn.digium.com/svn/asterisk/trunk@278957 f38db490-d61c-443f-a65b-d21fe96a405b --- CHANGES | 1 + configs/extconfig.conf.sample | 16 +++- configs/res_odbc.conf.sample | 69 ++++++++++++++-- include/asterisk/res_odbc.h | 23 ++++-- main/config.c | 143 ++++++++++++++++++++++++---------- res/res_config_odbc.c | 18 +++-- res/res_odbc.c | 66 +++++++++++++--- 7 files changed, 261 insertions(+), 75 deletions(-) diff --git a/CHANGES b/CHANGES index 7b866d2f5..ca2a153c7 100644 --- a/CHANGES +++ b/CHANGES @@ -524,6 +524,7 @@ Miscellaneous during device configuration. * The UNISTIM channel driver (chan_unistim) has been updated to support devices that have less than 3 lines on the LCD. + * Realtime now supports database failover. See the sample extconfig.conf for details. CLI Changes ----------- diff --git a/configs/extconfig.conf.sample b/configs/extconfig.conf.sample index 32ec6ad63..bde5d1268 100644 --- a/configs/extconfig.conf.sample +++ b/configs/extconfig.conf.sample @@ -9,7 +9,7 @@ ; ; Static configuration files: ; -; file.conf => driver,database[,table] +; file.conf => driver,database[,table[,priority]] ; ; maps a particular configuration file to the given ; database driver, database and table (or uses the @@ -40,14 +40,26 @@ ; database and table (or uses the name of ; the family if the table is not specified ; -;example => odbc,asterisk,alttable +;example => odbc,asterisk,alttable,1 +;example => mysql,asterisk,alttable,2 ;example2 => ldap,"dc=oxymium,dc=net",example2 ; +; Additionally, priorities are now supported for use as failover methods +; for retrieving realtime data. If one connection fails to retrieve any +; information, the next sequential priority will be tried next. This +; especially works well with ODBC connections, since res_odbc now caches +; when connection failures occur and prevents immediately retrying those +; connections until after a specified timeout. Note: priorities must +; start at 1 and be sequential (i.e. if you have only priorities 1, 2, +; and 4, then 4 will be ignored, because there is no 3). +; ; "odbc" is shown in the examples below, but is not the only valid realtime ; engine. There is: ; odbc ... res_config_odbc ; sqlite ... res_config_sqlite ; pgsql ... res_config_pgsql +; curl ... res_config_curl +; ldap ... res_config_ldap ; ;iaxusers => odbc,asterisk ;iaxpeers => odbc,asterisk diff --git a/configs/res_odbc.conf.sample b/configs/res_odbc.conf.sample index 8af0b9917..2980bad68 100644 --- a/configs/res_odbc.conf.sample +++ b/configs/res_odbc.conf.sample @@ -10,18 +10,69 @@ ; All other sections are arbitrary names for database connections. +; +; The context name is what will be used in other configuration files, such +; as extconfig.conf and func_odbc.conf, to reference this connection. [asterisk] +; +; Permit disabling sections without needing to comment them out. +; If not specified, it is assumed the section is enabled. enabled => no +; +; This value should match an entry in /etc/odbc.ini +; (or /usr/local/etc/odbc.ini, on FreeBSD and similar systems). dsn => asterisk +; +; Username for connecting to the database. The default user is "root". ;username => myuser +; +; Password for authenticating the user to the database. The default +; password is blank. ;password => mypass +; +; Build a connection at startup? pre-connect => yes ; ; What should we execute to ensure that our connection is still alive? The ; statement should return a non-zero value in the first field of its first ; record. The default is "select 1". ;sanitysql => select 1 - +; +; On some databases, the connection times out and a reconnection will be +; necessary. This setting configures the amount of time a connection +; may sit idle (in seconds) before a reconnection will be attempted. +;idlecheck => 3600 +; +; Should we use a single connection for all queries? Most databases will +; allow sharing the connection, though Sybase and MS SQL Server will not. +;share_connections => yes +; +; If we aren't sharing connections, what is the maximum number of connections +; that we should attempt? +;limit => 5 +; +; When the channel is destroyed, should any uncommitted open transactions +; automatically be committed? +;forcecommit => no +; +; How should we perceive data in other transactions within the database? +; Possible values are read_uncommitted, read_committed, repeatable_read, +; and serializable. The default is read_committed. +;isolation => repeatable_read +; +; Is the backslash a native escape character? The default is yes, but for +; MS SQL Server, the answer is no. +;backslash_is_escape => yes +; +; How long (in seconds) should we attempt to connect before considering the +; connection dead? The default is 10 seconds, but you may wish to reduce it, +; to increase responsiveness. +;connect_timeout => 10 +; +; When a connection fails, how long (in seconds) should we cache that +; information before we attempt another connection? This increases +; responsiveness, when a database resource is not working. +;negative_connection_cache => 300 [mysql2] enabled => no @@ -29,11 +80,6 @@ dsn => MySQL-asterisk username => myuser password => mypass pre-connect => yes -; -; On some databases, the connection times out and a reconnection will be -; necessary. This setting configures the amount of time a connection -; may sit idle (in seconds) before a reconnection will be attempted. -;idlecheck => 3600 ; Certain servers, such as MS SQL Server and Sybase use the TDS protocol, which ; limits the number of active queries per connection to 1. By telling res_odbc @@ -64,5 +110,12 @@ sanitysql => select count(*) from systables ; Server does not. backslash_is_escape => no - - +; +; If you are having problems with concurrency, please read this note from the +; mailing lists, regarding UnixODBC: +; +; http://lists.digium.com/pipermail/asterisk-dev/2009-February/036539.html +; +; In summary, try setting "Threading=2" in the relevant section within your +; odbcinst.ini. +; diff --git a/include/asterisk/res_odbc.h b/include/asterisk/res_odbc.h index 2bca41732..5760426ec 100644 --- a/include/asterisk/res_odbc.h +++ b/include/asterisk/res_odbc.h @@ -39,6 +39,7 @@ typedef enum { ODBC_SUCCESS=0, ODBC_FAIL=-1} odbc_status; enum { RES_ODBC_SANITY_CHECK = (1 << 0), RES_ODBC_INDEPENDENT_CONNECTION = (1 << 1), + RES_ODBC_CONNECTED = (1 << 2), }; /*! \brief ODBC container */ @@ -59,6 +60,7 @@ struct odbc_obj { AST_LIST_ENTRY(odbc_obj) list; }; +/*!\brief These structures are used for adaptive capabilities */ struct odbc_cache_columns { char *name; SQLSMALLINT type; @@ -99,17 +101,20 @@ struct odbc_cache_tables { */ int ast_odbc_smart_execute(struct odbc_obj *obj, SQLHSTMT stmt) __attribute__((deprecated)); -/*! +/*! * \brief Retrieves a connected ODBC object * \param name The name of the ODBC class for which a connection is needed. - * \param flags Set of flags used to control which connection is returned. - * \retval ODBC object - * \retval NULL if there is no connection available with the requested name. + * \param flags One or more of the following flags: + * \li RES_ODBC_SANITY_CHECK Whether to ensure that a connection is valid before returning the handle. Usually unnecessary. + * \li RES_ODBC_INDEPENDENT_CONNECTION Return a handle which is independent from all others. Usually used when starting a transaction. + * \li RES_ODBC_CONNECTED Only return a connected handle. Intended for use with peers which use idlecheck, which are checked periodically for reachability. + * \return ODBC object + * \retval NULL if there is no connection available with the requested name. * * Connection classes may, in fact, contain multiple connection handles. If * the connection is pooled, then each connection will be dedicated to the * thread which requests it. Note that all connections should be released - * when the thread is done by calling odbc_release_obj(), below. + * when the thread is done by calling ast_odbc_release_obj(), below. */ struct odbc_obj *_ast_odbc_request_obj2(const char *name, struct ast_flags flags, const char *file, const char *function, int lineno); struct odbc_obj *_ast_odbc_request_obj(const char *name, int check, const char *file, const char *function, int lineno); @@ -130,7 +135,7 @@ struct odbc_obj *_ast_odbc_request_obj(const char *name, int check, const char * struct odbc_obj *ast_odbc_retrieve_transaction_obj(struct ast_channel *chan, const char *objname); /*! - * \brief Releases an ODBC object previously allocated by odbc_request_obj() + * \brief Releases an ODBC object previously allocated by ast_odbc_request_obj() * \param obj The ODBC object */ void ast_odbc_release_obj(struct odbc_obj *obj); @@ -175,7 +180,9 @@ SQLHSTMT ast_odbc_prepare_and_execute(struct odbc_obj *obj, SQLHSTMT (*prepare_c * \param tablename Tablename to describe * \retval A structure describing the table layout, or NULL, if the table is not found or another error occurs. * When a structure is returned, the contained columns list will be - * rdlock'ed, to ensure that it will be retained in memory. + * rdlock'ed, to ensure that it will be retained in memory. The information + * will be cached until a reload event or when ast_odbc_clear_cache() is called + * with the relevant parameters. * \since 1.6.1 */ struct odbc_cache_tables *ast_odbc_find_table(const char *database, const char *tablename); @@ -191,6 +198,8 @@ struct odbc_cache_columns *ast_odbc_find_column(struct odbc_cache_tables *table, /*! * \brief Remove a cache entry from memory + * This function may be called to clear entries created and cached by the + * ast_odbc_find_table() API call. * \param database Name of an ODBC class (used to ensure like-named tables in different databases are not confused) * \param tablename Tablename for which a cached record should be removed * \retval 0 if the cache entry was removed, or -1 if no matching entry was found. diff --git a/main/config.c b/main/config.c index 1ed46b77c..34e4247a7 100644 --- a/main/config.c +++ b/main/config.c @@ -163,6 +163,7 @@ static int hashtab_compare_strings(void *a, void *b, int flags) static struct ast_config_map { struct ast_config_map *next; + int priority; char *name; char *driver; char *database; @@ -1858,7 +1859,7 @@ static void clear_config_maps(void) ast_mutex_unlock(&config_lock); } -static int append_mapping(const char *name, const char *driver, const char *database, const char *table) +static int append_mapping(const char *name, const char *driver, const char *database, const char *table, int priority) { struct ast_config_map *map; int length; @@ -1883,6 +1884,7 @@ static int append_mapping(const char *name, const char *driver, const char *data map->table = map->database + strlen(map->database) + 1; strcpy(map->table, table); } + map->priority = priority; map->next = config_maps; ast_verb(2, "Binding %s to %s/%s/%s\n", map->name, map->driver, map->database, map->table ? map->table : map->name); @@ -1895,8 +1897,9 @@ int read_config_maps(void) { struct ast_config *config, *configtmp; struct ast_variable *v; - char *driver, *table, *database, *stringp, *tmp; + char *driver, *table, *database, *textpri, *stringp, *tmp; struct ast_flags flags = { CONFIG_FLAG_NOREALTIME }; + int pri; clear_config_maps(); @@ -1930,6 +1933,10 @@ int read_config_maps(void) } table = strsep(&stringp, ","); + textpri = strsep(&stringp, ","); + if (!textpri || !(pri = atoi(textpri))) { + pri = 1; + } if (!strcmp(v->name, extconfig_conf)) { ast_log(LOG_WARNING, "Cannot bind '%s'!\n", extconfig_conf); @@ -1950,14 +1957,14 @@ int read_config_maps(void) continue; if (!strcasecmp(v->name, "sipfriends")) { ast_log(LOG_WARNING, "The 'sipfriends' table is obsolete, update your config to use sipusers and sippeers, though they can point to the same table.\n"); - append_mapping("sipusers", driver, database, table ? table : "sipfriends"); - append_mapping("sippeers", driver, database, table ? table : "sipfriends"); + append_mapping("sipusers", driver, database, table ? table : "sipfriends", pri); + append_mapping("sippeers", driver, database, table ? table : "sipfriends", pri); } else if (!strcasecmp(v->name, "iaxfriends")) { ast_log(LOG_WARNING, "The 'iaxfriends' table is obsolete, update your config to use iaxusers and iaxpeers, though they can point to the same table.\n"); - append_mapping("iaxusers", driver, database, table ? table : "iaxfriends"); - append_mapping("iaxpeers", driver, database, table ? table : "iaxfriends"); + append_mapping("iaxusers", driver, database, table ? table : "iaxfriends", pri); + append_mapping("iaxpeers", driver, database, table ? table : "iaxfriends", pri); } else - append_mapping(v->name, driver, database, table); + append_mapping(v->name, driver, database, table, pri); } ast_config_destroy(config); @@ -2006,7 +2013,7 @@ int ast_config_engine_deregister(struct ast_config_engine *del) } /*! \brief Find realtime engine for realtime family */ -static struct ast_config_engine *find_engine(const char *family, char *database, int dbsiz, char *table, int tabsiz) +static struct ast_config_engine *find_engine(const char *family, int priority, char *database, int dbsiz, char *table, int tabsiz) { struct ast_config_engine *eng, *ret = NULL; struct ast_config_map *map; @@ -2014,7 +2021,7 @@ static struct ast_config_engine *find_engine(const char *family, char *database, ast_mutex_lock(&config_lock); for (map = config_maps; map; map = map->next) { - if (!strcasecmp(family, map->name)) { + if (!strcasecmp(family, map->name) && (priority == map->priority)) { if (database) ast_copy_string(database, map->database, dbsiz); if (table) @@ -2063,13 +2070,13 @@ struct ast_config *ast_config_internal_load(const char *filename, struct ast_con if (!ast_test_flag(&flags, CONFIG_FLAG_NOREALTIME) && config_engine_list) { struct ast_config_engine *eng; - eng = find_engine(filename, db, sizeof(db), table, sizeof(table)); + eng = find_engine(filename, 1, db, sizeof(db), table, sizeof(table)); if (eng && eng->load_func) { loader = eng; } else { - eng = find_engine("global", db, sizeof(db), table, sizeof(table)); + eng = find_engine("global", 1, db, sizeof(db), table, sizeof(table)); if (eng && eng->load_func) loader = eng; } @@ -2107,10 +2114,17 @@ static struct ast_variable *ast_load_realtime_helper(const char *family, va_list char db[256]; char table[256]; struct ast_variable *res=NULL; + int i; - eng = find_engine(family, db, sizeof(db), table, sizeof(table)); - if (eng && eng->realtime_func) - res = eng->realtime_func(db, table, ap); + for (i = 1; ; i++) { + if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) { + if (eng->realtime_func && (res = eng->realtime_func(db, table, ap))) { + return res; + } + } else { + return NULL; + } + } return res; } @@ -2168,7 +2182,7 @@ int ast_check_realtime(const char *family) return 0; /* There are no engines at all so fail early */ } - eng = find_engine(family, NULL, 0, NULL, 0); + eng = find_engine(family, 1, NULL, 0, NULL, 0); if (eng) return 1; return 0; @@ -2186,12 +2200,18 @@ int ast_realtime_require_field(const char *family, ...) char db[256]; char table[256]; va_list ap; - int res = -1; + int res = -1, i; va_start(ap, family); - eng = find_engine(family, db, sizeof(db), table, sizeof(table)); - if (eng && eng->require_func) { - res = eng->require_func(db, table, ap); + for (i = 1; ; i++) { + if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) { + /* If the require succeeds, it returns 0. */ + if (eng->require_func && !(res = eng->require_func(db, table, ap))) { + break; + } + } else { + break; + } } va_end(ap); @@ -2203,11 +2223,17 @@ int ast_unload_realtime(const char *family) struct ast_config_engine *eng; char db[256]; char table[256]; - int res = -1; + int res = -1, i; - eng = find_engine(family, db, sizeof(db), table, sizeof(table)); - if (eng && eng->unload_func) { - res = eng->unload_func(db, table); + for (i = 1; ; i++) { + if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) { + if (eng->unload_func) { + /* Do this for ALL engines */ + res = eng->unload_func(db, table); + } + } else { + break; + } } return res; } @@ -2219,11 +2245,18 @@ struct ast_config *ast_load_realtime_multientry(const char *family, ...) char table[256]; struct ast_config *res = NULL; va_list ap; + int i; va_start(ap, family); - eng = find_engine(family, db, sizeof(db), table, sizeof(table)); - if (eng && eng->realtime_multi_func) - res = eng->realtime_multi_func(db, table, ap); + for (i = 1; ; i++) { + if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) { + if (eng->realtime_multi_func && (res = eng->realtime_multi_func(db, table, ap))) { + break; + } + } else { + break; + } + } va_end(ap); return res; @@ -2232,15 +2265,22 @@ struct ast_config *ast_load_realtime_multientry(const char *family, ...) int ast_update_realtime(const char *family, const char *keyfield, const char *lookup, ...) { struct ast_config_engine *eng; - int res = -1; + int res = -1, i; char db[256]; char table[256]; va_list ap; va_start(ap, lookup); - eng = find_engine(family, db, sizeof(db), table, sizeof(table)); - if (eng && eng->update_func) - res = eng->update_func(db, table, keyfield, lookup, ap); + for (i = 1; ; i++) { + if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) { + /* If the update succeeds, it returns 0. */ + if (eng->update_func && !(res = eng->update_func(db, table, keyfield, lookup, ap))) { + break; + } + } else { + break; + } + } va_end(ap); return res; @@ -2249,15 +2289,21 @@ int ast_update_realtime(const char *family, const char *keyfield, const char *lo int ast_update2_realtime(const char *family, ...) { struct ast_config_engine *eng; - int res = -1; + int res = -1, i; char db[256]; char table[256]; va_list ap; va_start(ap, family); - eng = find_engine(family, db, sizeof(db), table, sizeof(table)); - if (eng && eng->update2_func) - res = eng->update2_func(db, table, ap); + for (i = 1; ; i++) { + if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) { + if (eng->update2_func && !(res = eng->update2_func(db, table, ap))) { + break; + } + } else { + break; + } + } va_end(ap); return res; @@ -2266,15 +2312,22 @@ int ast_update2_realtime(const char *family, ...) int ast_store_realtime(const char *family, ...) { struct ast_config_engine *eng; - int res = -1; + int res = -1, i; char db[256]; char table[256]; va_list ap; va_start(ap, family); - eng = find_engine(family, db, sizeof(db), table, sizeof(table)); - if (eng && eng->store_func) - res = eng->store_func(db, table, ap); + for (i = 1; ; i++) { + if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) { + /* If the store succeeds, it returns 0. */ + if (eng->store_func && !(res = eng->store_func(db, table, ap))) { + break; + } + } else { + break; + } + } va_end(ap); return res; @@ -2283,15 +2336,21 @@ int ast_store_realtime(const char *family, ...) int ast_destroy_realtime(const char *family, const char *keyfield, const char *lookup, ...) { struct ast_config_engine *eng; - int res = -1; + int res = -1, i; char db[256]; char table[256]; va_list ap; va_start(ap, lookup); - eng = find_engine(family, db, sizeof(db), table, sizeof(table)); - if (eng && eng->destroy_func) - res = eng->destroy_func(db, table, keyfield, lookup, ap); + for (i = 1; ; i++) { + if ((eng = find_engine(family, i, db, sizeof(db), table, sizeof(table)))) { + if (eng->destroy_func && !(res = eng->destroy_func(db, table, keyfield, lookup, ap))) { + break; + } + } else { + break; + } + } va_end(ap); return res; diff --git a/res/res_config_odbc.c b/res/res_config_odbc.c index 749872303..15534c7fa 100644 --- a/res/res_config_odbc.c +++ b/res/res_config_odbc.c @@ -167,6 +167,7 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl SQLLEN indicator; va_list aq; struct custom_prepare_struct cps = { .sql = sql }; + struct ast_flags connected_flag = { RES_ODBC_CONNECTED }; if (ast_string_field_init(&cps, 256)) { return NULL; @@ -179,7 +180,7 @@ static struct ast_variable *realtime_odbc(const char *database, const char *tabl return NULL; } - obj = ast_odbc_request_obj(database, 0); + obj = ast_odbc_request_obj2(database, connected_flag); if (!obj) { ast_log(LOG_ERROR, "No database handle available with the name of '%s' (check res_odbc.conf)\n", database); @@ -325,6 +326,7 @@ static struct ast_config *realtime_multi_odbc(const char *database, const char * struct ast_variable *var=NULL; struct ast_config *cfg=NULL; struct ast_category *cat=NULL; + struct ast_flags connected_flag = { RES_ODBC_CONNECTED }; SQLULEN colsize; SQLSMALLINT colcount=0; SQLSMALLINT datatype; @@ -341,7 +343,7 @@ static struct ast_config *realtime_multi_odbc(const char *database, const char * va_copy(aq, ap); - obj = ast_odbc_request_obj(database, 0); + obj = ast_odbc_request_obj2(database, connected_flag); if (!obj) { ast_string_field_free_memory(&cps); return NULL; @@ -479,6 +481,7 @@ static int update_odbc(const char *database, const char *table, const char *keyf struct custom_prepare_struct cps = { .sql = sql, .extra = lookup }; struct odbc_cache_tables *tableptr; struct odbc_cache_columns *column; + struct ast_flags connected_flag = { RES_ODBC_CONNECTED }; if (!table) { return -1; @@ -492,7 +495,7 @@ static int update_odbc(const char *database, const char *table, const char *keyf } tableptr = ast_odbc_find_table(database, table); - if (!(obj = ast_odbc_request_obj(database, 0))) { + if (!(obj = ast_odbc_request_obj2(database, connected_flag))) { ast_odbc_release_table(tableptr); ast_string_field_free_memory(&cps); return -1; @@ -716,6 +719,7 @@ static int store_odbc(const char *database, const char *table, va_list ap) int res; va_list aq; struct custom_prepare_struct cps = { .sql = sql, .extra = NULL }; + struct ast_flags connected_flag = { RES_ODBC_CONNECTED }; va_copy(cps.ap, ap); va_copy(aq, ap); @@ -723,7 +727,7 @@ static int store_odbc(const char *database, const char *table, va_list ap) if (!table) return -1; - obj = ast_odbc_request_obj(database, 0); + obj = ast_odbc_request_obj2(database, connected_flag); if (!obj) return -1; @@ -790,6 +794,7 @@ static int destroy_odbc(const char *database, const char *table, const char *key int res; va_list aq; struct custom_prepare_struct cps = { .sql = sql, .extra = lookup }; + struct ast_flags connected_flag = { RES_ODBC_CONNECTED }; va_copy(cps.ap, ap); va_copy(aq, ap); @@ -797,7 +802,7 @@ static int destroy_odbc(const char *database, const char *table, const char *key if (!table) return -1; - obj = ast_odbc_request_obj(database, 0); + obj = ast_odbc_request_obj2(database, connected_flag); if (!obj) return -1; @@ -883,13 +888,14 @@ static struct ast_config *config_odbc(const char *database, const char *table, c char last[128] = ""; struct config_odbc_obj q; struct ast_flags loader_flags = { 0 }; + struct ast_flags connected_flag = { RES_ODBC_CONNECTED }; memset(&q, 0, sizeof(q)); if (!file || !strcmp (file, "res_config_odbc.conf")) return NULL; /* cant configure myself with myself ! */ - obj = ast_odbc_request_obj(database, 0); + obj = ast_odbc_request_obj2(database, connected_flag); if (!obj) return NULL; diff --git a/res/res_odbc.c b/res/res_odbc.c index ec1591c00..495f83f05 100644 --- a/res/res_odbc.c +++ b/res/res_odbc.c @@ -131,6 +131,12 @@ struct odbc_class unsigned int limit; /*!< Maximum number of database handles we will allow */ int count; /*!< Running count of pooled connections */ unsigned int idlecheck; /*!< Recheck the connection if it is idle for this long (in seconds) */ + unsigned int conntimeout; /*!< Maximum time the connection process should take */ + /*! When a connection fails, cache that failure for how long? */ + struct timeval negative_connection_cache; + /*! When a connection fails, when did that last occur? */ + struct timeval last_negative_connect; + /*! List of handles associated with this class */ struct ao2_container *obj_container; }; @@ -661,7 +667,7 @@ SQLHSTMT ast_odbc_prepare_and_execute(struct odbc_obj *obj, SQLHSTMT (*prepare_c return stmt; } -int ast_odbc_smart_execute(struct odbc_obj *obj, SQLHSTMT stmt) +int ast_odbc_smart_execute(struct odbc_obj *obj, SQLHSTMT stmt) { int res = 0, i; SQLINTEGER nativeerror=0, numfields=0; @@ -681,9 +687,10 @@ int ast_odbc_smart_execute(struct odbc_obj *obj, SQLHSTMT stmt) } } } - } else + } else { obj->last_used = ast_tvnow(); - + } + return res; } @@ -746,7 +753,8 @@ static int load_odbc_config(void) struct ast_variable *v; char *cat; const char *dsn, *username, *password, *sanitysql; - int enabled, pooling, limit, bse, forcecommit, isolation; + int enabled, pooling, limit, bse, conntimeout, forcecommit, isolation; + struct timeval ncache = { 0, 0 }; unsigned int idlecheck; int preconnect = 0, res = 0; struct ast_flags config_flags = { 0 }; @@ -808,6 +816,22 @@ static int load_odbc_config(void) sanitysql = v->value; } else if (!strcasecmp(v->name, "backslash_is_escape")) { bse = ast_true(v->value); + } else if (!strcasecmp(v->name, "connect_timeout")) { + if (sscanf(v->value, "%d", &conntimeout) != 1 || conntimeout < 1) { + ast_log(LOG_WARNING, "connect_timeout must be a positive integer\n"); + conntimeout = 10; + } + } else if (!strcasecmp(v->name, "negative_connection_cache")) { + double dncache; + if (sscanf(v->value, "%lf", &dncache) != 1 || dncache < 0) { + ast_log(LOG_WARNING, "negative_connection_cache must be a non-negative integer\n"); + /* 5 minutes sounds like a reasonable default */ + ncache.tv_sec = 300; + ncache.tv_usec = 0; + } else { + ncache.tv_sec = (int)dncache; + ncache.tv_usec = (dncache - ncache.tv_sec) * 1000000; + } } else if (!strcasecmp(v->name, "forcecommit")) { forcecommit = ast_true(v->value); } else if (!strcasecmp(v->name, "isolation")) { @@ -851,6 +875,8 @@ static int load_odbc_config(void) new->forcecommit = forcecommit ? 1 : 0; new->isolation = isolation; new->idlecheck = idlecheck; + new->conntimeout = conntimeout; + new->negative_connection_cache = ncache; if (cat) ast_copy_string(new->name, cat, sizeof(new->name)); @@ -923,7 +949,13 @@ static char *handle_cli_odbc_show(struct ast_cli_entry *e, int cmd, struct ast_c 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; + char timestr[80]; + struct ast_tm tm; + + ast_localtime(&class->last_negative_connect, &tm, NULL); + ast_strftime(timestr, sizeof(timestr), "%Y-%m-%d %T", &tm); ast_cli(a->fd, " Name: %s\n DSN: %s\n", class->name, class->dsn); + ast_cli(a->fd, " Last connection attempt: %s\n", timestr); if (class->haspool) { struct ao2_iterator aoi2 = ao2_iterator_init(class->obj_container, 0); @@ -1171,6 +1203,7 @@ struct odbc_obj *_ast_odbc_request_obj2(const char *name, struct ast_flags flags unsigned char state[10], diagnostic[256]; if (!(class = ao2_callback(class_container, 0, aoro2_class_cb, (char *) name))) { + ast_debug(1, "Class not found!\n"); return NULL; } @@ -1183,11 +1216,13 @@ struct odbc_obj *_ast_odbc_request_obj2(const char *name, struct ast_flags flags if (obj) { ast_assert(ao2_ref(obj, 0) > 1); } - - if (!obj && (class->count < class->limit)) { + if (!obj && (class->count < class->limit) && + (time(NULL) > class->last_negative_connect.tv_sec + class->negative_connection_cache.tv_sec)) { obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor); if (!obj) { + class->count--; ao2_ref(class, -1); + ast_debug(3, "Unable to allocate object\n"); return NULL; } ast_assert(ao2_ref(obj, 0) == 1); @@ -1228,9 +1263,11 @@ struct odbc_obj *_ast_odbc_request_obj2(const char *name, struct ast_flags flags } 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))) { + ast_debug(1, "Object not found\n"); obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor); if (!obj) { ao2_ref(class, -1); + ast_debug(3, "Unable to allocate object\n"); return NULL; } ast_mutex_init(&obj->lock); @@ -1271,6 +1308,7 @@ struct odbc_obj *_ast_odbc_request_obj2(const char *name, struct ast_flags flags if (!(obj = ao2_alloc(sizeof(*obj), odbc_obj_destructor))) { ast_assert(ao2_ref(class, 0) > 1); ao2_ref(class, -1); + ast_debug(3, "Unable to allocate object\n"); return NULL; } ast_mutex_init(&obj->lock); @@ -1313,10 +1351,16 @@ struct odbc_obj *_ast_odbc_request_obj2(const char *name, struct ast_flags flags } } - if (obj && ast_test_flag(&flags, RES_ODBC_SANITY_CHECK)) { + if (obj && ast_test_flag(&flags, RES_ODBC_CONNECTED) && !obj->up) { + /* Check if this connection qualifies for reconnection, with negative connection cache time */ + if (time(NULL) > class->last_negative_connect.tv_sec + class->negative_connection_cache.tv_sec) { + odbc_obj_connect(obj); + } + } else if (obj && ast_test_flag(&flags, RES_ODBC_SANITY_CHECK)) { ast_odbc_sanity_check(obj); - } else if (obj && obj->parent->idlecheck > 0 && ast_tvdiff_sec(ast_tvnow(), obj->last_used) > obj->parent->idlecheck) + } else if (obj && obj->parent->idlecheck > 0 && ast_tvdiff_sec(ast_tvnow(), obj->last_used) > obj->parent->idlecheck) { odbc_obj_connect(obj); + } #ifdef DEBUG_THREADS if (obj) { @@ -1431,11 +1475,12 @@ static odbc_status odbc_obj_connect(struct odbc_obj *obj) if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { ast_log(LOG_WARNING, "res_odbc: Error AllocHDB %d\n", res); + obj->parent->last_negative_connect = ast_tvnow(); ast_mutex_unlock(&obj->lock); return ODBC_FAIL; } - SQLSetConnectAttr(obj->con, SQL_LOGIN_TIMEOUT, (SQLPOINTER *) 10, 0); - SQLSetConnectAttr(obj->con, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER *) 10, 0); + SQLSetConnectAttr(obj->con, SQL_LOGIN_TIMEOUT, (SQLPOINTER *) obj->parent->conntimeout, 0); + SQLSetConnectAttr(obj->con, SQL_ATTR_CONNECTION_TIMEOUT, (SQLPOINTER *) obj->parent->conntimeout, 0); #ifdef NEEDTRACE SQLSetConnectAttr(obj->con, SQL_ATTR_TRACE, &enable, SQL_IS_INTEGER); SQLSetConnectAttr(obj->con, SQL_ATTR_TRACEFILE, tracefile, strlen(tracefile)); @@ -1448,6 +1493,7 @@ static odbc_status odbc_obj_connect(struct odbc_obj *obj) if ((res != SQL_SUCCESS) && (res != SQL_SUCCESS_WITH_INFO)) { SQLGetDiagRec(SQL_HANDLE_DBC, obj->con, 1, state, &err, msg, 100, &mlen); + obj->parent->last_negative_connect = ast_tvnow(); ast_mutex_unlock(&obj->lock); ast_log(LOG_WARNING, "res_odbc: Error SQLConnect=%d errno=%d %s\n", res, (int)err, msg); return ODBC_FAIL;