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"); +} + + +
+
+