From 256274ac330963a2a4afec64d322e4988ae2e512 Mon Sep 17 00:00:00 2001 From: Rupa Schomaker Date: Wed, 24 Mar 2010 16:11:24 +0000 Subject: [PATCH] MODAPP-412 - improve rate limit support add switch_core_hash_delete_multi git-svn-id: http://svn.freeswitch.org/svn/freeswitch/trunk@17085 d0543943-73ff-0310-b7d9-9358b9ac24b2 --- src/include/switch_core.h | 8 +++ src/include/switch_types.h | 2 + src/mod/applications/mod_limit/mod_limit.c | 67 +++++++++++++++++++--- src/switch_core_hash.c | 36 ++++++++++++ 4 files changed, 104 insertions(+), 9 deletions(-) diff --git a/src/include/switch_core.h b/src/include/switch_core.h index bb2c58daea..7fb24783e9 100644 --- a/src/include/switch_core.h +++ b/src/include/switch_core.h @@ -1201,6 +1201,14 @@ SWITCH_DECLARE(switch_status_t) switch_core_hash_delete(_In_ switch_hash_t *hash */ SWITCH_DECLARE(switch_status_t) switch_core_hash_delete_locked(_In_ switch_hash_t *hash, _In_z_ const char *key, _In_ switch_mutex_t *mutex); +/*! + \brief Delete data from a hash based on callback function + \param hash the hash to delete from + \param callback the function to call which returns SWITCH_TRUE to delete, SWITCH_FALSE to preserve + \return SWITCH_STATUS_SUCCESS if any data is deleted +*/ +SWITCH_DECLARE(switch_status_t) switch_core_hash_delete_multi(_In_ switch_hash_t *hash, _In_ switch_hash_delete_callback_t callback, _In_opt_ void *pData); + /*! \brief Retrieve data from a given hash \param hash the hash to retrieve from diff --git a/src/include/switch_types.h b/src/include/switch_types.h index ae0f7be561..397175c0db 100644 --- a/src/include/switch_types.h +++ b/src/include/switch_types.h @@ -1560,6 +1560,8 @@ typedef void (*switch_event_callback_t) (switch_event_t *); typedef switch_caller_extension_t *(*switch_dialplan_hunt_function_t) (switch_core_session_t *, void *, switch_caller_profile_t *); #define SWITCH_STANDARD_DIALPLAN(name) static switch_caller_extension_t *name (switch_core_session_t *session, void *arg, switch_caller_profile_t *caller_profile) +typedef switch_bool_t (*switch_hash_delete_callback_t) (_In_ const void *key, _In_ const void *val, _In_opt_ void *pData); +#define SWITCH_HASH_DELETE_FUNC(name) static switch_bool_t name (const void *key, const void *val, void *pData) typedef struct switch_scheduler_task switch_scheduler_task_t; diff --git a/src/mod/applications/mod_limit/mod_limit.c b/src/mod/applications/mod_limit/mod_limit.c index 44d900c8bf..b32cb129a9 100644 --- a/src/mod/applications/mod_limit/mod_limit.c +++ b/src/mod/applications/mod_limit/mod_limit.c @@ -37,6 +37,7 @@ #define LIMIT_EVENT_USAGE "limit::usage" #define LIMIT_IGNORE_TRANSFER_VARIABLE "limit_ignore_transfer" +#define LIMIT_HASH_CLEANUP_INTERVAL 900 SWITCH_MODULE_LOAD_FUNCTION(mod_limit_load); SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_limit_shutdown); @@ -60,6 +61,7 @@ typedef struct { uint32_t total_usage; uint32_t rate_usage; time_t last_check; + uint32_t interval; } limit_hash_item_t; typedef struct { @@ -280,7 +282,9 @@ static switch_status_t hash_state_handler(switch_core_session_t *session) switch_hash_index_t *hi; switch_mutex_lock(globals.limit_hash_mutex); - /* Loop through the channel's hashtable which contains mapping to all the limit_hash_item_t referenced by that channel */ + /* Loop through the channel's hashtable which contains mapping to all the limit_hash_item_t referenced by that channel + while() idiom used -- 'cause pvt->hash is being completely removed + */ while ((hi = switch_hash_first(NULL, pvt->hash))) { void *val = NULL; const void *key; @@ -293,7 +297,7 @@ static switch_status_t hash_state_handler(switch_core_session_t *session) item->total_usage--; switch_log_printf(SWITCH_CHANNEL_SESSION_LOG(session), SWITCH_LOG_INFO, "Usage for %s is now %d\n", (const char *) key, item->total_usage); - if (item->total_usage == 0) { + if (item->total_usage == 0 && item->rate_usage == 0) { /* Noone is using this item anymore */ switch_core_hash_delete(globals.limit_hash, (const char *) key); free(item); @@ -956,6 +960,8 @@ static switch_bool_t do_limit_hash(switch_core_session_t *session, const char *r } if (interval > 0) { + /* interval is always the last used interval setting? */ + item->interval = interval; if (item->last_check <= (now - interval)) { item->rate_usage = 1; item->last_check = now; @@ -1012,6 +1018,36 @@ static switch_bool_t do_limit_hash(switch_core_session_t *session, const char *r return status; } +SWITCH_HASH_DELETE_FUNC(limit_hash_cleanup_delete_callback) { + limit_hash_item_t *item = (limit_hash_item_t *) val; + time_t now = switch_epoch_time_now(NULL); + + /* reset to 0 if window has passed so we can clean it up */ + if (item->rate_usage > 0 && (item->last_check <= (now - item->interval))) { + item->rate_usage = 0; + } + + if (item->total_usage == 0 && item->rate_usage == 0) { + /* Noone is using this item anymore */ + switch_log_printf(SWITCH_CHANNEL_LOG, SWITCH_LOG_DEBUG, "Freeing limit item: %s\n", (const char *) key); + + free(item); + return SWITCH_TRUE; + } + + return SWITCH_FALSE; +} + +/* !\brief Periodically checks for unused limit entries and frees them */ +SWITCH_STANDARD_SCHED_FUNC(limit_hash_cleanup_callback) +{ + switch_mutex_lock(globals.limit_hash_mutex); + switch_core_hash_delete_multi(globals.limit_hash, limit_hash_cleanup_delete_callback, NULL); + switch_mutex_unlock(globals.limit_hash_mutex); + + task->runtime = switch_epoch_time_now(NULL) + LIMIT_HASH_CLEANUP_INTERVAL; +} + /* !\brief Releases usage of a limit_hash-controlled ressource */ static void limit_hash_release(switch_core_session_t *session, const char *realm, const char *id) { @@ -1034,7 +1070,7 @@ static void limit_hash_release(switch_core_session_t *session, const char *realm switch_core_hash_delete(pvt->hash, hashkey); - if (item->total_usage == 0) { + if (item->total_usage == 0 && item->rate_usage == 0) { /* Noone is using this item anymore */ switch_core_hash_delete(globals.limit_hash, (const char *) hashkey); free(item); @@ -1172,15 +1208,16 @@ SWITCH_STANDARD_APP(limit_hash_execute_function) } -#define LIMIT_HASH_USAGE_USAGE " " +#define LIMIT_HASH_USAGE_USAGE " [rate]" SWITCH_STANDARD_API(limit_hash_usage_function) { int argc = 0; - char *argv[3] = { 0 }; + char *argv[4] = { 0 }; char *mydata = NULL; char *hash_key = NULL; limit_hash_item_t *item = NULL; - uint32_t count = 0; + uint32_t count = 0, rcount = 0; + switch_bool_t dorate = SWITCH_FALSE; switch_mutex_lock(globals.limit_hash_mutex); @@ -1195,13 +1232,24 @@ SWITCH_STANDARD_API(limit_hash_usage_function) goto end; } + if (argc > 2) { + if (!strcasecmp(argv[2], "rate")) { + dorate = SWITCH_TRUE; + } + } + hash_key = switch_mprintf("%s_%s", argv[0], argv[1]); if ((item = switch_core_hash_find(globals.limit_hash, hash_key))) { count = item->total_usage; + rcount = item->rate_usage; } - stream->write_function(stream, "%d", count); + if (dorate == SWITCH_TRUE) { + stream->write_function(stream, "%d/%d", count, rcount); + } else { + stream->write_function(stream, "%d", count); + } end: switch_safe_free(mydata); @@ -1240,7 +1288,8 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_limit_load) /* connect my internal structure to the blank pointer passed to me */ *module_interface = switch_loadable_module_create_module_interface(pool, modname); - + switch_scheduler_add_task(switch_epoch_time_now(NULL) + LIMIT_HASH_CLEANUP_INTERVAL, limit_hash_cleanup_callback, "limit_hash_cleanup", "mod_limit", 0, NULL, + SSHF_NONE); SWITCH_ADD_APP(app_interface, "limit", "Limit", LIMIT_DESC, limit_function, LIMIT_USAGE, SAF_SUPPORT_NOMEDIA); SWITCH_ADD_APP(app_interface, "limit_execute", "Limit", LIMITEXECUTE_USAGE, limit_execute_function, LIMITEXECUTE_USAGE, SAF_SUPPORT_NOMEDIA); @@ -1274,7 +1323,7 @@ SWITCH_MODULE_LOAD_FUNCTION(mod_limit_load) SWITCH_MODULE_SHUTDOWN_FUNCTION(mod_limit_shutdown) { - + switch_scheduler_del_task_group("mod_limit"); switch_event_free_subclass(LIMIT_EVENT_USAGE); switch_xml_config_cleanup(config_settings); diff --git a/src/switch_core_hash.c b/src/switch_core_hash.c index 35c1232e28..2b62258bd1 100644 --- a/src/switch_core_hash.c +++ b/src/switch_core_hash.c @@ -117,6 +117,42 @@ SWITCH_DECLARE(switch_status_t) switch_core_hash_delete_locked(switch_hash_t *ha return SWITCH_STATUS_SUCCESS; } +SWITCH_DECLARE(switch_status_t) switch_core_hash_delete_multi(switch_hash_t *hash, switch_hash_delete_callback_t callback, void *pData) { + + switch_hash_index_t *hi = NULL; + switch_event_t *event = NULL; + switch_event_header_t *header = NULL; + switch_status_t status = SWITCH_STATUS_GENERR; + + switch_event_create_subclass(&event, SWITCH_EVENT_CLONE, NULL); + switch_assert(event); + + /* iterate through the hash, call callback, if callback returns true, put the key on the list (event) + When done, iterate through the list deleting hash entries + */ + + for (hi = switch_hash_first(NULL, hash); hi; hi = switch_hash_next(hi)) { + const void *key; + void *val; + switch_hash_this(hi, &key, NULL, &val); + if (callback(key, val, pData)) { + switch_event_add_header_string(event, SWITCH_STACK_BOTTOM, "delete", (const char *) key); + } + } + + /* now delete them */ + for (header = event->headers; header; header = header->next) { + if (switch_core_hash_delete(hash, header->value) == SWITCH_STATUS_SUCCESS) { + status = SWITCH_STATUS_SUCCESS; + } + } + + switch_event_destroy(&event); + + return status; +} + + SWITCH_DECLARE(void *) switch_core_hash_find(switch_hash_t *hash, const char *key) { return sqlite3HashFind(&hash->table, key, (int) strlen(key) + 1);