From 955ea1e5f02f8c4d91b9bea1eb4163aff81e46c4 Mon Sep 17 00:00:00 2001 From: Jaco Kroon Date: Fri, 13 Dec 2024 00:35:55 +0200 Subject: [PATCH] res_odbc: cache_size option to limit the cached connections. UserNote: New cache_size option for res_odbc to on a per class basis limit the number of cached connections. Please reference the sample configuration for details. Signed-off-by: Jaco Kroon --- configs/samples/res_odbc.conf.sample | 7 +++++ res/res_odbc.c | 38 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/configs/samples/res_odbc.conf.sample b/configs/samples/res_odbc.conf.sample index b73ea0c25a..47f1baa992 100644 --- a/configs/samples/res_odbc.conf.sample +++ b/configs/samples/res_odbc.conf.sample @@ -88,6 +88,13 @@ pre-connect => yes ; compatible, setting this value to any of rr, roundrobin or queue will result ; in a round-robin queue being used. ;cache_type => roundrobin +; +; Limit the number of connections to cache. On extremely spiky loads this could be +; useful to limit the overall number of connections that's kept open. The default is +; to not limit the cache size (thus effectively limit to max_connections). When a returned +; connection would result in the cache size being exceeded, the longest idle connection +; in the cache shall be dropped. +;cache_size => 10 [mysql2] enabled => no diff --git a/res/res_odbc.c b/res/res_odbc.c index ea44e6f1bc..9fd5022e59 100644 --- a/res/res_odbc.c +++ b/res/res_odbc.c @@ -101,6 +101,10 @@ struct odbc_class char *sql_text; /*! Slow query limit (in milliseconds) */ unsigned int slowquerylimit; + /*! Maximum number of cached connections, default is maxconnections */ + unsigned int max_cache_size; + /*! Current cached connection count, when cache_size will exceed max_cache_size, longest-idle connection will be dropped from the cache */ + unsigned int cur_cache; }; static struct ao2_container *class_container; @@ -564,6 +568,7 @@ static int load_odbc_config(void) struct timeval ncache = { 0, 0 }; int preconnect = 0, res = 0, cache_is_queue = 0; struct ast_flags config_flags = { 0 }; + unsigned int max_cache_size; struct odbc_class *new; @@ -591,6 +596,7 @@ static int load_odbc_config(void) logging = 0; slowquerylimit = 5000; cache_is_queue = 0; + max_cache_size = UINT_MAX; for (v = ast_variable_browse(config, cat); v; v = v->next) { if (!strcasecmp(v->name, "pooling") || !strncasecmp(v->name, "share", 5) || @@ -650,6 +656,10 @@ static int load_odbc_config(void) cache_is_queue = !strcasecmp(v->value, "rr") || !strcasecmp(v->value, "roundrobin") || !strcasecmp(v->value, "queue"); + } else if (!strcasecmp(v->name, "cache_size")) { + if (sscanf(v->value, "%u", &max_cache_size) != 1) { + ast_log(LOG_WARNING, "cache_size must be a non-negative integer\n"); + } } } @@ -679,6 +689,8 @@ static int load_odbc_config(void) new->logging = logging; new->slowquerylimit = slowquerylimit; new->cache_is_queue = cache_is_queue; + new->max_cache_size = max_cache_size; + new->cur_cache = 0; if (cat) ast_copy_string(new->name, cat, sizeof(new->name)); @@ -766,6 +778,8 @@ static char *handle_cli_odbc_show(struct ast_cli_entry *e, int cmd, struct ast_c ast_cli(a->fd, " Number of active connections: %zd (out of %d)\n", class->connection_cnt, class->maxconnections); ast_cli(a->fd, " Cache Type: %s\n", class->cache_is_queue ? "round-robin queue" : "stack (last release, first re-use)"); + ast_cli(a->fd, " Cache Usage: %u cached out of %u\n", class->cur_cache, + class->max_cache_size < class->maxconnections ? class->max_cache_size : class->maxconnections); ast_cli(a->fd, " Logging: %s\n", class->logging ? "Enabled" : "Disabled"); if (class->logging) { ast_cli(a->fd, " Number of prepares executed: %d\n", class->prepares_executed); @@ -836,6 +850,28 @@ void ast_odbc_release_obj(struct odbc_obj *obj) } else { AST_LIST_INSERT_HEAD(&class->connections, obj, list); } + + if (class->cur_cache >= class->max_cache_size) { + /* cache is full */ + if (class->cache_is_queue) { + /* HEAD will be oldest */ + obj = AST_LIST_REMOVE_HEAD(&class->connections, list); + } else { + /* TAIL will be oldest */ + obj = AST_LIST_LAST(&class->connections); + AST_LIST_REMOVE(&class->connections, obj, list); + } + --class->connection_cnt; + ast_mutex_unlock(&class->lock); + + ast_debug(2, "ODBC Pool '%s' exceeded cache size, dropping '%p', connection count is %zd (%u cached)\n", + class->name, obj, class->connection_cnt, class->cur_cache); + + ao2_ref(obj, -1); + + ast_mutex_lock(&class->lock); + } else + ++class->cur_cache; ast_cond_signal(&class->cond); ast_mutex_unlock(&class->lock); @@ -931,6 +967,8 @@ struct odbc_obj *_ast_odbc_request_obj2(const char *name, struct ast_flags flags ast_mutex_lock(&class->lock); obj = AST_LIST_REMOVE_HEAD(&class->connections, list); + if (obj) + --class->cur_cache; ast_mutex_unlock(&class->lock);