mirror of
https://github.com/asterisk/asterisk.git
synced 2025-09-30 02:26:23 +00:00
sorcery: Prevent duplicate objects and ensure missing objects are created on update
This patch resolves two issues in Sorcery objectset handling with multiple backends: 1. Prevent duplicate objects: When an object exists in more than one backend (e.g., a contact in both 'astdb' and 'realtime'), the objectset previously returned multiple instances of the same logical object. This caused logic failures in components like the PJSIP registrar, where duplicate contact entries led to overcounting and incorrect deletions, when max_contacts=1 and remove_existing=yes. This patch ensures only one instance of an object with a given key is added to the objectset, avoiding these duplicate-related side effects. 2. Ensure missing objects are created: When using multiple writable backends, a temporary backend failure can lead to objects missing permanently from that backend. Currently, .update() silently fails if the object is not present, and no .create() is attempted. This results in inconsistent state across backends (e.g. astdb vs. realtime). This patch introduces a new global option in sorcery.conf: [general] update_or_create_on_update_miss = yes|no Default: no (preserves existing behavior). When enabled: if .update() fails with no data found, .create() is attempted in that backend. This ensures that objects missing due to temporary backend outages are re-synchronized once the backend is available again. Added a new CLI command: sorcery show settings Displays global Sorcery settings, including the current value of update_or_create_on_update_miss. Updated tests to validate both flag enabled/disabled behavior. Fixes: #1289 UserNote: Users relying on Sorcery multiple writable backends configurations (e.g., astdb + realtime) may now enable update_or_create_on_update_miss = yes in sorcery.conf to ensure missing objects are recreated after temporary backend failures. Default behavior remains unchanged unless explicitly enabled.
This commit is contained in:
@@ -42,6 +42,7 @@
|
||||
#include "asterisk/threadpool.h"
|
||||
#include "asterisk/json.h"
|
||||
#include "asterisk/vector.h"
|
||||
#include "asterisk/cli.h"
|
||||
|
||||
/* To prevent DEBUG_FD_LEAKS from interfering with things we undef open and close */
|
||||
#undef open
|
||||
@@ -283,6 +284,9 @@ struct ao2_container *observers;
|
||||
/*! \brief Registered sorcery instances */
|
||||
static struct ao2_container *instances;
|
||||
|
||||
/* \brief Global config flag (declared in sorcery.h) */
|
||||
int ast_sorcery_update_or_create_on_update_miss = 0;
|
||||
|
||||
static int int_handler_fn(const void *obj, const intptr_t *args, char **buf)
|
||||
{
|
||||
int *field = (int *)(obj + args[0]);
|
||||
@@ -365,9 +369,39 @@ AO2_STRING_FIELD_CMP_FN(ast_sorcery_internal_wizard, callbacks.name)
|
||||
AO2_STRING_FIELD_HASH_FN(ast_sorcery_object_field, name)
|
||||
AO2_STRING_FIELD_CMP_FN(ast_sorcery_object_field, name)
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief CLI command implementation for 'sorcery show settings'
|
||||
*/
|
||||
static char *cli_show_settings(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
|
||||
{
|
||||
switch (cmd) {
|
||||
case CLI_INIT:
|
||||
e->command = "sorcery show settings";
|
||||
e->usage = "Usage: sorcery show settings\n"
|
||||
" Show global configuration options\n";
|
||||
return NULL;
|
||||
case CLI_GENERATE:
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ast_cli(a->fd, "\nSorcery global settings\n");
|
||||
ast_cli(a->fd, "-----------------\n");
|
||||
ast_cli(a->fd, " Update->Create fallback in backends: %s\n",
|
||||
ast_sorcery_update_or_create_on_update_miss ? "enabled" : "disabled");
|
||||
|
||||
return CLI_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
static struct ast_cli_entry cli_commands[] = {
|
||||
AST_CLI_DEFINE(cli_show_settings, "Show global configuration options"),
|
||||
};
|
||||
|
||||
/*! \brief Cleanup function for graceful shutdowns */
|
||||
static void sorcery_cleanup(void)
|
||||
{
|
||||
ast_cli_unregister_multiple(cli_commands, ARRAY_LEN(cli_commands));
|
||||
ast_threadpool_shutdown(threadpool);
|
||||
threadpool = NULL;
|
||||
ao2_cleanup(wizards);
|
||||
@@ -384,6 +418,29 @@ AO2_STRING_FIELD_CMP_FN(sorcery_proxy, module_name)
|
||||
/*! \brief Hashing function for sorcery instances */
|
||||
AO2_STRING_FIELD_HASH_FN(sorcery_proxy, module_name)
|
||||
|
||||
/*!
|
||||
* \internal
|
||||
* \brief Parse [general] options from sorcery.conf and set globals.
|
||||
*/
|
||||
static void parse_general_options(void)
|
||||
{
|
||||
struct ast_flags flags = { 0 };
|
||||
struct ast_config *cfg = ast_config_load2("sorcery.conf", "sorcery", flags);
|
||||
const struct ast_variable *var;
|
||||
|
||||
if (!cfg || cfg == CONFIG_STATUS_FILEINVALID) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var = ast_variable_browse(cfg, "general"); var; var = var->next) {
|
||||
if (!strcasecmp(var->name, "update_or_create_on_update_miss")) {
|
||||
ast_sorcery_update_or_create_on_update_miss = ast_true(var->value);
|
||||
}
|
||||
}
|
||||
|
||||
ast_config_destroy(cfg);
|
||||
}
|
||||
|
||||
int ast_sorcery_init(void)
|
||||
{
|
||||
struct ast_threadpool_options options = {
|
||||
@@ -395,6 +452,8 @@ int ast_sorcery_init(void)
|
||||
};
|
||||
ast_assert(wizards == NULL);
|
||||
|
||||
parse_general_options();
|
||||
|
||||
threadpool = ast_threadpool_create("sorcery", NULL, &options);
|
||||
if (!threadpool) {
|
||||
return -1;
|
||||
@@ -417,6 +476,10 @@ int ast_sorcery_init(void)
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ast_cli_register_multiple(cli_commands, ARRAY_LEN(cli_commands))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
ast_register_cleanup(sorcery_cleanup);
|
||||
|
||||
return 0;
|
||||
@@ -1907,7 +1970,7 @@ void *ast_sorcery_retrieve_by_fields(const struct ast_sorcery *sorcery, const ch
|
||||
|
||||
/* If returning multiple objects create a container to store them in */
|
||||
if ((flags & AST_RETRIEVE_FLAG_MULTIPLE)) {
|
||||
object = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, NULL, NULL);
|
||||
object = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, ast_sorcery_object_id_sort, ast_sorcery_object_id_compare);
|
||||
if (!object) {
|
||||
return NULL;
|
||||
}
|
||||
@@ -1961,7 +2024,7 @@ struct ao2_container *ast_sorcery_retrieve_by_regex(const struct ast_sorcery *so
|
||||
return NULL;
|
||||
}
|
||||
|
||||
objects = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, NULL, NULL);
|
||||
objects = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, ast_sorcery_object_id_sort, ast_sorcery_object_id_compare);
|
||||
if (!objects) {
|
||||
return NULL;
|
||||
}
|
||||
@@ -1996,7 +2059,7 @@ struct ao2_container *ast_sorcery_retrieve_by_prefix(const struct ast_sorcery *s
|
||||
return NULL;
|
||||
}
|
||||
|
||||
objects = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, 0, NULL, NULL);
|
||||
objects = ao2_container_alloc_list(AO2_ALLOC_OPT_LOCK_NOLOCK, AO2_CONTAINER_ALLOC_OPT_DUPS_REJECT, ast_sorcery_object_id_sort, ast_sorcery_object_id_compare);
|
||||
if (!objects) {
|
||||
return NULL;
|
||||
}
|
||||
|
Reference in New Issue
Block a user