diff --git a/include/asterisk/sorcery.h b/include/asterisk/sorcery.h index 0bd22a6340..d681ebb5ab 100644 --- a/include/asterisk/sorcery.h +++ b/include/asterisk/sorcery.h @@ -312,6 +312,9 @@ struct ast_sorcery_wizard { /*! \brief Callback for closing a wizard */ void (*close)(void *data); + + /* \brief Callback for whether or not the wizard believes the object is stale */ + int (*is_stale)(const struct ast_sorcery *sorcery, void *data, void *object); }; /*! \brief Interface for a sorcery object type observer */ @@ -1201,6 +1204,20 @@ int ast_sorcery_update(const struct ast_sorcery *sorcery, void *object); */ int ast_sorcery_delete(const struct ast_sorcery *sorcery, void *object); +/*! + * \brief Determine if a sorcery object is stale with respect to its backing datastore + * \since 14.0.0 + * + * This function will query the wizard(s) backing the particular sorcery object to + * determine if the in-memory object is now stale. No action is taken to update + * the object. Callers of this function may use one of the ast_sorcery_retrieve + * functions to obtain a new instance of the object if desired. + * + * \retval 0 the object is not stale + * \retval 1 the object is stale + */ +int ast_sorcery_is_stale(const struct ast_sorcery *sorcery, void *object); + /*! * \brief Decrease the reference count of a sorcery structure * @@ -1217,6 +1234,16 @@ void ast_sorcery_unref(struct ast_sorcery *sorcery); */ const char *ast_sorcery_object_get_id(const void *object); +/*! + * \since 14.0.0 + * \brief Get when the socery object was created + * + * \param object Pointer to a sorcery object + * + * \retval The time when the object was created + */ +const struct timeval ast_sorcery_object_get_created(const void *object); + /*! * \brief Get the type of a sorcery object * diff --git a/main/sorcery.c b/main/sorcery.c index 20b3d6b2f1..fbbd146624 100644 --- a/main/sorcery.c +++ b/main/sorcery.c @@ -129,6 +129,9 @@ struct ast_sorcery_object { /*! \brief Extended object fields */ struct ast_variable *extended; + + /*! \brief Time that the object was created */ + struct timeval created; }; /*! \brief Structure for registered object type */ @@ -1734,6 +1737,7 @@ void *ast_sorcery_alloc(const struct ast_sorcery *sorcery, const char *type, con details->object->id = ast_strdup(id); } + details->object->created = ast_tvnow(); ast_copy_string(details->object->type, type, sizeof(details->object->type)); if (aco_set_defaults(&object_type->type, id, details)) { @@ -2143,6 +2147,35 @@ int ast_sorcery_delete(const struct ast_sorcery *sorcery, void *object) return object_wizard ? 0 : -1; } +int ast_sorcery_is_stale(const struct ast_sorcery *sorcery, void *object) +{ + const struct ast_sorcery_object_details *details = object; + RAII_VAR(struct ast_sorcery_object_type *, object_type, ao2_find(sorcery->types, details->object->type, OBJ_KEY), ao2_cleanup); + struct ast_sorcery_object_wizard *found_wizard; + int res = 0; + int i; + + if (!object_type) { + return -1; + } + + AST_VECTOR_RW_RDLOCK(&object_type->wizards); + for (i = 0; i < AST_VECTOR_SIZE(&object_type->wizards); i++) { + found_wizard = AST_VECTOR_GET(&object_type->wizards, i); + + if (found_wizard->wizard->callbacks.is_stale) { + res |= found_wizard->wizard->callbacks.is_stale(sorcery, found_wizard->data, object); + ast_debug(5, "After calling wizard '%s', object '%s' is %s\n", + found_wizard->wizard->callbacks.name, + ast_sorcery_object_get_id(object), + res ? "stale" : "not stale"); + } + } + AST_VECTOR_RW_UNLOCK(&object_type->wizards); + + return res; +} + void ast_sorcery_unref(struct ast_sorcery *sorcery) { if (sorcery) { @@ -2161,6 +2194,12 @@ const char *ast_sorcery_object_get_id(const void *object) return details->object->id; } +const struct timeval ast_sorcery_object_get_created(const void *object) +{ + const struct ast_sorcery_object_details *details = object; + return details->object->created; +} + const char *ast_sorcery_object_get_type(const void *object) { const struct ast_sorcery_object_details *details = object; diff --git a/tests/test_sorcery.c b/tests/test_sorcery.c index b6b3d09c91..5d96422aa9 100644 --- a/tests/test_sorcery.c +++ b/tests/test_sorcery.c @@ -138,6 +138,9 @@ struct sorcery_test_caching { /*! \brief Whether the object has been deleted from the cache or not */ unsigned int deleted:1; + /*! \brief Whether the object is stale or not */ + unsigned int is_stale:1; + /*! \brief Object to return when asked */ struct test_sorcery_object object; }; @@ -217,6 +220,12 @@ static int sorcery_test_delete(const struct ast_sorcery *sorcery, void *data, vo return 0; } +static int sorcery_test_is_stale(const struct ast_sorcery *sorcery, void *data, void *object) +{ + cache.is_stale = 1; + return 1; +} + /*! \brief Dummy sorcery wizards, not actually used so we only populate the name and nothing else */ static struct ast_sorcery_wizard test_wizard = { .name = "test", @@ -234,6 +243,7 @@ static struct ast_sorcery_wizard test_wizard2 = { .retrieve_id = sorcery_test_retrieve_id, .update = sorcery_test_update, .delete = sorcery_test_delete, + .is_stale = sorcery_test_is_stale, }; static void sorcery_observer_created(const void *object) @@ -2223,6 +2233,84 @@ AST_TEST_DEFINE(object_delete_uncreated) return AST_TEST_PASS; } +AST_TEST_DEFINE(object_is_stale) +{ + RAII_VAR(struct ast_sorcery *, sorcery, NULL, ast_sorcery_unref); + RAII_VAR(struct ast_sorcery_wizard *, wizard1, &test_wizard, ast_sorcery_wizard_unregister); + RAII_VAR(struct ast_sorcery_wizard *, wizard2, &test_wizard2, ast_sorcery_wizard_unregister); + RAII_VAR(struct test_sorcery_object *, obj1, NULL, ao2_cleanup); + RAII_VAR(struct test_sorcery_object *, obj2, NULL, ao2_cleanup); + + switch (cmd) { + case TEST_INIT: + info->name = "object_is_stale"; + info->category = "/main/sorcery/"; + info->summary = "sorcery object staleness unit test"; + info->description = + "Test whether sorcery will query a wizard correctly if asked\n" + "if an object is stale."; + return AST_TEST_NOT_RUN; + case TEST_EXECUTE: + break; + } + + if (ast_sorcery_wizard_register(&test_wizard)) { + ast_test_status_update(test, "Failed to register a perfectly valid sorcery wizard\n"); + return AST_TEST_FAIL; + } + + if (ast_sorcery_wizard_register(&test_wizard2)) { + ast_test_status_update(test, "Failed to register a perfectly valid sorcery wizard\n"); + return AST_TEST_FAIL; + } + + if (!(sorcery = ast_sorcery_open())) { + ast_test_status_update(test, "Failed to open sorcery structure\n"); + return AST_TEST_FAIL; + } + + if ((ast_sorcery_apply_default(sorcery, "test", "test", NULL) != AST_SORCERY_APPLY_SUCCESS) || + ast_sorcery_internal_object_register(sorcery, "test", test_sorcery_object_alloc, NULL, NULL)) { + return AST_TEST_FAIL; + } + + ast_sorcery_object_field_register_nodoc(sorcery, "test", "bob", "5", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, bob)); + ast_sorcery_object_field_register_nodoc(sorcery, "test", "joe", "10", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, joe)); + ast_sorcery_object_field_register_custom_nodoc(sorcery, "test", "jim", "444", jim_handler, NULL, jim_vl, 0, 0); + ast_sorcery_object_field_register_custom_nodoc(sorcery, "test", "jack", "888,999", jack_handler, jack_str, NULL, 0, 0); + + + if ((ast_sorcery_apply_default(sorcery, "test2", "test2", "test2data") != AST_SORCERY_APPLY_SUCCESS) || + ast_sorcery_internal_object_register(sorcery, "test2", test_sorcery_object_alloc, NULL, NULL)) { + return AST_TEST_FAIL; + } + + ast_sorcery_object_field_register_nodoc(sorcery, "test2", "bob", "5", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, bob)); + ast_sorcery_object_field_register_nodoc(sorcery, "test2", "joe", "10", OPT_UINT_T, 0, FLDSET(struct test_sorcery_object, joe)); + ast_sorcery_object_field_register_custom_nodoc(sorcery, "test2", "jim", "444", jim_handler, NULL, jim_vl, 0, 0); + ast_sorcery_object_field_register_custom_nodoc(sorcery, "test2", "jack", "888,999", jack_handler, jack_str, NULL, 0, 0); + + + if (!(obj1 = ast_sorcery_alloc(sorcery, "test", "blah"))) { + ast_test_status_update(test, "Failed to allocate a known object type\n"); + return AST_TEST_FAIL; + } + + if (!(obj2 = ast_sorcery_alloc(sorcery, "test2", "blah"))) { + ast_test_status_update(test, "Failed to allocate a known object type\n"); + return AST_TEST_FAIL; + } + + /* The 'test' wizard has no is_stale callback */ + ast_test_validate(test, ast_sorcery_is_stale(sorcery, obj1) == 0); + + /* The 'test2' wizard should return stale */ + ast_test_validate(test, ast_sorcery_is_stale(sorcery, obj2) == 1); + ast_test_validate(test, cache.is_stale == 1); + + return AST_TEST_PASS; +} + AST_TEST_DEFINE(caching_wizard_behavior) { struct ast_flags flags = { CONFIG_FLAG_NOCACHE }; @@ -3505,6 +3593,7 @@ static int unload_module(void) AST_TEST_UNREGISTER(object_update_uncreated); AST_TEST_UNREGISTER(object_delete); AST_TEST_UNREGISTER(object_delete_uncreated); + AST_TEST_UNREGISTER(object_is_stale); AST_TEST_UNREGISTER(caching_wizard_behavior); AST_TEST_UNREGISTER(object_type_observer); AST_TEST_UNREGISTER(configuration_file_wizard); @@ -3561,6 +3650,7 @@ static int load_module(void) AST_TEST_REGISTER(object_update_uncreated); AST_TEST_REGISTER(object_delete); AST_TEST_REGISTER(object_delete_uncreated); + AST_TEST_REGISTER(object_is_stale); AST_TEST_REGISTER(caching_wizard_behavior); AST_TEST_REGISTER(object_type_observer); AST_TEST_REGISTER(configuration_file_wizard);