From 61998e9ed11f89766cac07e44636dfe81e279b4f Mon Sep 17 00:00:00 2001 From: rdondeti Date: Sun, 29 Mar 2026 11:45:12 -0500 Subject: [PATCH] cachedb_local: raise event on cache entry expiry Add E_CACHEDB_LOCAL_EXPIRED event support, allowing users to be notified via event_route when cached entries expire. The event is raised during the periodic cleanup timer (cache_clean_period) with the following parameters: - key: the expired cache key - value: the expired cache value - collection: the cache collection name Example usage in the OpenSIPS script: event_route[E_CACHEDB_LOCAL_EXPIRED] { xlog("expired: $param(key) = $param(value)\n"); } The event is raised outside the per-bucket hash table lock to avoid deadlocks when the event_route handler accesses the cache. Expired entry data is copied to pkg memory before lock release and freed after the event is dispatched. Uses the standard EVI (Event Interface) framework following the same pattern as usrloc and dialog modules. An evi_probe_event() check avoids parameter setup overhead when no subscribers exist. Closes #3735 --- modules/cachedb_local/cachedb_local.c | 44 +++++++++ modules/cachedb_local/cachedb_local_evi.c | 93 +++++++++++++++++++ modules/cachedb_local/cachedb_local_evi.h | 39 ++++++++ .../cachedb_local/doc/cachedb_local_admin.xml | 39 ++++++++ 4 files changed, 215 insertions(+) create mode 100644 modules/cachedb_local/cachedb_local_evi.c create mode 100644 modules/cachedb_local/cachedb_local_evi.h diff --git a/modules/cachedb_local/cachedb_local.c b/modules/cachedb_local/cachedb_local.c index 4dff4aee408..082c2436690 100644 --- a/modules/cachedb_local/cachedb_local.c +++ b/modules/cachedb_local/cachedb_local.c @@ -42,6 +42,7 @@ #include "cachedb_local.h" #include "cachedb_local_replication.h" +#include "cachedb_local_evi.h" #include "hash.h" #include "../../mem/rpm_mem.h" @@ -743,6 +744,11 @@ static int mod_init(void) } } + if (lcache_event_init() < 0) { + LM_ERR("failed to init cachedb_local events\n"); + return -1; + } + /* register timer to delete the expired entries */ register_timer("localcache-expire",localcache_clean, 0, cache_clean_period, TIMER_FLAG_DELAY_ON_DELAY); @@ -815,12 +821,20 @@ static void destroy(void) } } +/* temporary list node for collecting expired entries outside the lock */ +struct expired_ev { + str key; + str value; + struct expired_ev *next; +}; + void localcache_clean(unsigned int ticks,void *param) { int i; lcache_entry_t* me1, *me2; lcache_col_t* it; lcache_t* cache_htable; + struct expired_ev *ev_list, *ev, *ev_next; for ( it=lcache_collection; it; it=it->next ) { LM_DBG("start\n"); @@ -828,6 +842,8 @@ void localcache_clean(unsigned int ticks,void *param) for(i = 0; i< it->col_htable->size; i++) { + ev_list = NULL; + lock_get(&cache_htable[i].lock); me1 = cache_htable[i].entries; me2 = NULL; @@ -839,6 +855,25 @@ void localcache_clean(unsigned int ticks,void *param) LM_DBG("deleted entry attr= [%.*s]\n", me1->attr.len, me1->attr.s); + /* save key/value for event after lock release */ + if (ei_lcache_expired_id != EVI_ERROR) { + ev = pkg_malloc(sizeof(*ev) + + me1->attr.len + me1->value.len); + if (ev) { + ev->key.s = (char *)(ev + 1); + ev->key.len = me1->attr.len; + memcpy(ev->key.s, me1->attr.s, + me1->attr.len); + ev->value.s = ev->key.s + + me1->attr.len; + ev->value.len = me1->value.len; + memcpy(ev->value.s, me1->value.s, + me1->value.len); + ev->next = ev_list; + ev_list = ev; + } + } + if(me2) { me2->next = me1->next; @@ -863,6 +898,15 @@ void localcache_clean(unsigned int ticks,void *param) } lock_release(&cache_htable[i].lock); + + /* raise events outside the lock to avoid deadlocks + * if the event_route handler accesses the cache */ + for (ev = ev_list; ev; ev = ev_next) { + ev_next = ev->next; + lcache_raise_expired_event(&ev->key, + &ev->value, &it->col_name); + pkg_free(ev); + } } } } diff --git a/modules/cachedb_local/cachedb_local_evi.c b/modules/cachedb_local/cachedb_local_evi.c new file mode 100644 index 00000000000..baddaabad72 --- /dev/null +++ b/modules/cachedb_local/cachedb_local_evi.c @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2026 OpenSIPS Solutions + * + * This file is part of opensips, a free SIP server. + * + * opensips is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * opensips is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "cachedb_local_evi.h" +#include "../../dprint.h" +#include "../../mem/mem.h" + +event_id_t ei_lcache_expired_id = EVI_ERROR; + +static evi_params_p lcache_expired_params; +static evi_param_p lcache_ev_key; +static evi_param_p lcache_ev_value; +static evi_param_p lcache_ev_collection; + +int lcache_event_init(void) +{ + ei_lcache_expired_id = evi_publish_event( + str_init(LCACHE_EV_EXPIRED)); + if (ei_lcache_expired_id == EVI_ERROR) { + LM_ERR("cannot register %s event\n", LCACHE_EV_EXPIRED); + return -1; + } + + lcache_expired_params = pkg_malloc(sizeof(evi_params_t)); + if (!lcache_expired_params) { + LM_ERR("no more pkg memory\n"); + return -1; + } + memset(lcache_expired_params, 0, sizeof(evi_params_t)); + + lcache_ev_key = evi_param_create(lcache_expired_params, + _str(LCACHE_EV_PARAM_KEY)); + if (!lcache_ev_key) + goto error; + + lcache_ev_value = evi_param_create(lcache_expired_params, + _str(LCACHE_EV_PARAM_VALUE)); + if (!lcache_ev_value) + goto error; + + lcache_ev_collection = evi_param_create(lcache_expired_params, + _str(LCACHE_EV_PARAM_COLLECTION)); + if (!lcache_ev_collection) + goto error; + + return 0; + +error: + LM_ERR("cannot create event parameter\n"); + return -1; +} + +void lcache_raise_expired_event(const str *key, const str *value, + const str *collection) +{ + if (ei_lcache_expired_id == EVI_ERROR || !evi_probe_event(ei_lcache_expired_id)) + return; + + if (evi_param_set_str(lcache_ev_key, key) < 0) { + LM_ERR("cannot set key parameter\n"); + return; + } + + if (evi_param_set_str(lcache_ev_value, value) < 0) { + LM_ERR("cannot set value parameter\n"); + return; + } + + if (evi_param_set_str(lcache_ev_collection, collection) < 0) { + LM_ERR("cannot set collection parameter\n"); + return; + } + + if (evi_raise_event(ei_lcache_expired_id, lcache_expired_params) < 0) + LM_ERR("cannot raise %s event\n", LCACHE_EV_EXPIRED); +} diff --git a/modules/cachedb_local/cachedb_local_evi.h b/modules/cachedb_local/cachedb_local_evi.h new file mode 100644 index 00000000000..9eb79798e6d --- /dev/null +++ b/modules/cachedb_local/cachedb_local_evi.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2026 OpenSIPS Solutions + * + * This file is part of opensips, a free SIP server. + * + * opensips is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * opensips is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef _CACHEDB_LOCAL_EVI_H_ +#define _CACHEDB_LOCAL_EVI_H_ + +#include "../../evi/evi_modules.h" +#include "../../evi/evi_params.h" + +#define LCACHE_EV_EXPIRED "E_CACHEDB_LOCAL_EXPIRED" + +#define LCACHE_EV_PARAM_KEY "key" +#define LCACHE_EV_PARAM_VALUE "value" +#define LCACHE_EV_PARAM_COLLECTION "collection" + +extern event_id_t ei_lcache_expired_id; + +int lcache_event_init(void); +void lcache_raise_expired_event(const str *key, const str *value, + const str *collection); + +#endif /* _CACHEDB_LOCAL_EVI_H_ */ diff --git a/modules/cachedb_local/doc/cachedb_local_admin.xml b/modules/cachedb_local/doc/cachedb_local_admin.xml index e57d7503125..5f04af82789 100644 --- a/modules/cachedb_local/doc/cachedb_local_admin.xml +++ b/modules/cachedb_local/doc/cachedb_local_admin.xml @@ -390,4 +390,43 @@ opensips-cli -x mi cachedb_local:fetch_chunk "keyprefix*" collection +
+ Exported Events +
+ + <function moreinfo="none">E_CACHEDB_LOCAL_EXPIRED</function> + + + This event is raised when a cached entry expires and is + removed during the periodic cleanup (controlled by + cache_clean_period). + + Parameters: + + + key - The key of the + expired cache entry. + + + value - The value of the + expired cache entry. + + + collection - The name of + the cache collection. + + + + E_CACHEDB_LOCAL_EXPIRED usage + +event_route[E_CACHEDB_LOCAL_EXPIRED] { + xlog("cache expired: $param(key) = $param(value) " + "in collection $param(collection)\n"); +} + + +
+
+