This commit is contained in:
InterLinked1 2024-03-20 19:36:46 +00:00 committed by GitHub
commit 872ca2c0cf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 208 additions and 19 deletions

View File

@ -149,6 +149,20 @@ enum ast_module_helper_type {
*/
enum ast_module_load_result ast_load_resource(const char *resource_name);
/*!
* \brief Unload and load a module again.
* \param resource_name The name of the module to unload.
* \param ast_module_unload_mode The force flag. This should be set using one of the AST_FORCE flags.
* \param recursive Attempt to recursively unload any dependents of this module
* if that will allow the module to unload, and load them back again afterwards.
*
*
* \retval 0 on success.
* \retval 1 on error unloading modules.
* \retval -1 on error loading modules back.
*/
int ast_refresh_resource(const char *resource_name, enum ast_module_unload_mode force, int recursive);
/*!
* \brief Unload a module.
* \param resource_name The name of the module to unload.

View File

@ -807,30 +807,35 @@ static char *handle_logger_mute(struct ast_cli_entry *e, int cmd, struct ast_cli
static char *handle_refresh(struct ast_cli_entry *e, int cmd, struct ast_cli_args *a)
{
static const char * const completions[] = { "recursively", NULL };
int res;
/* "module refresh <mod>" */
switch (cmd) {
case CLI_INIT:
e->command = "module refresh";
e->usage =
"Usage: module refresh <module name>\n"
" Unloads and loads the specified module into Asterisk.\n";
"Usage: module refresh <module name> [recursively]\n"
" Unloads and loads the specified module into Asterisk.\n"
" 'recursively' will attempt to unload any modules with\n"
" dependencies on this module for you and load them again\n"
" afterwards.\n";
return NULL;
case CLI_GENERATE:
if (a->pos != e->args) {
return NULL;
if (a->pos == e->args) {
return ast_module_helper(a->line, a->word, a->pos, a->n, a->pos, AST_MODULE_HELPER_UNLOAD);
} else if (a->pos == e->args + 1) {
return ast_cli_complete(a->word, completions, a->n);
}
return ast_module_helper(a->line, a->word, a->pos, a->n, a->pos, AST_MODULE_HELPER_UNLOAD);
return NULL;
}
if (a->argc != e->args + 1) {
if (a->argc < 3 || a->argc > 4) {
return CLI_SHOWUSAGE;
}
if (ast_unload_resource(a->argv[e->args], AST_FORCE_SOFT)) {
ast_cli(a->fd, "Unable to unload resource %s\n", a->argv[e->args]);
return CLI_FAILURE;
}
if (ast_load_resource(a->argv[e->args])) {
ast_cli(a->fd, "Unable to load module %s\n", a->argv[e->args]);
res = ast_refresh_resource(a->argv[e->args], AST_FORCE_SOFT, a->argc == 4 && !strcasecmp(a->argv[3], "automatically"));
if (res) {
ast_cli(a->fd, "Unable to %s resource %s\n", res > 0 ? "unload" : "load", a->argv[e->args]);
return CLI_FAILURE;
}
ast_cli(a->fd, "Unloaded and loaded %s\n", a->argv[e->args]);

View File

@ -1216,7 +1216,73 @@ int modules_shutdown(void)
return !res;
}
int ast_unload_resource(const char *resource_name, enum ast_module_unload_mode force)
/*!
* \brief Whether or not this module should be able to be unloaded successfully,
* if we recursively unload any modules that are dependent on it.
* \note module_list should be locked when calling this
* \retval 0 if not, 1 if likely possible
*/
static int graceful_unload_possible(struct ast_module *target, struct ast_vector_const_string *dependents)
{
struct ast_module *mod;
int usecount = target->usecount;
/* Check the reffed_deps of each module to see if we're one of them. */
AST_DLLIST_TRAVERSE(&module_list, mod, entry) {
if (AST_VECTOR_GET_CMP(&mod->reffed_deps, target, AST_VECTOR_ELEM_DEFAULT_CMP)) {
const char *name;
/* This module is dependent on the target.
* If we can unload this module gracefully,
* then that would decrement our use count.
* If any single module could not be unloaded gracefully,
* then we don't proceed. */
int unloadable;
if (AST_VECTOR_GET_CMP(dependents, ast_module_name(mod), !strcasecmp)) {
/* Already in our list, we already checked this module,
* and we gave it the green light. */
ast_debug(3, "Skipping duplicate dependent %s\n", ast_module_name(mod));
if (!--usecount) {
break;
}
continue;
}
unloadable = graceful_unload_possible(mod, dependents);
if (!unloadable) {
ast_log(LOG_NOTICE, "Can't unload %s right now because %s is dependent on it\n", ast_module_name(target), ast_module_name(mod));
return 0;
}
/* Insert at beginning, so later if we're loading modules again automatically, we can do so in the same order. */
name = ast_strdup(ast_module_name(mod));
if (!name) {
return 0;
}
ast_debug(3, "Found new dependent %s\n", ast_module_name(mod));
if (AST_VECTOR_INSERT_AT(dependents, 0, name)) {
ast_log(LOG_ERROR, "Failed to add module '%s' to vector\n", ast_module_name(mod));
return 0;
}
if (!--usecount) {
break;
}
}
}
if (usecount) {
ast_log(LOG_NOTICE, "Module %s cannot be unloaded (would still have use count %d/%d after unloading dependents)\n", ast_module_name(target), usecount, target->usecount);
return 0;
}
return 1;
}
/*!
* \brief Unload a resource
* \param resource_name Module name
* \param force
* \param autounload Whether to attempt to automatically unload dependents of this module and load them again afterwards
* \param dependents. Can be NULL if autounload is 0.
* \retval 0 on success, -1 on failure
*/
static int auto_unload_resource(const char *resource_name, enum ast_module_unload_mode force, int autounload, struct ast_vector_const_string *dependents)
{
struct ast_module *mod;
int res = -1;
@ -1244,13 +1310,55 @@ int ast_unload_resource(const char *resource_name, enum ast_module_unload_mode f
goto exit; /* Skip all the intervening !error checks, only the last one is relevant. */
}
if (!error && mod->usecount > 0 && autounload) {
/* Try automatically unloading all modules dependent on the module we're trying to unload,
* and then, optionally, load them back again if we end up loading this module again.
* If any modules that have us as a dependency can't be unloaded, for whatever reason,
* then the entire unload operation will fail, so to try to make this an atomic operation
* and avoid leaving modules in a partial unload state, first check if we think we're going
* to be able to pull this off, and if not, abort.
*
* A race condition is technically still possible, if some depending module suddenly goes in use
* between this check and trying to unload it, but this takes care of the majority of
* easy-to-avoid cases that would fail eventually anyways.
*
* Note that we can encounter false negatives (e.g. unloading all the dependents would allow
* a module to unload, but graceful_unload_possible returns 0). This is because it's only
* checking direct module dependencies; other dependencies caused by a module registering
* a resource that cause its ref count to get bumped aren't accounted for here.
*/
if (graceful_unload_possible(mod, dependents)) {
int i, res = 0;
size_t num_deps = AST_VECTOR_SIZE(dependents);
ast_debug(1, "%lu module%s will need to be unloaded\n", AST_VECTOR_SIZE(dependents), ESS(AST_VECTOR_SIZE(dependents)));
/* Unload from the end, since the last module was the first one added, which means it isn't a dependency of anything else. */
for (i = AST_VECTOR_SIZE(dependents) - 1; i >= 0; i--) {
const char *depname = AST_VECTOR_GET(dependents, i);
res = ast_unload_resource(depname, force);
if (res) {
ast_log(LOG_WARNING, "Failed to unload %lu module%s automatically (%s could not be unloaded)\n", num_deps, ESS(num_deps), depname);
/* To be polite, load modules that we already unloaded,
* to try to leave things the way they were when we started. */
for (i++; i < num_deps; i++) {
const char *depname = AST_VECTOR_GET(dependents, i);
res = ast_load_resource(depname);
if (res) {
ast_log(LOG_WARNING, "Could not load module '%s' again automatically\n", depname);
}
}
break;
}
}
/* Either we failed, or we successfully unloaded everything.
* If we succeeded, we can now proceed and unload ourselves. */
}
}
if (!error && (mod->usecount > 0)) {
if (force)
ast_log(LOG_WARNING, "Warning: Forcing removal of module '%s' with use count %d\n",
resource_name, mod->usecount);
else {
ast_log(LOG_WARNING, "Soft unload failed, '%s' has use count %d\n", resource_name,
mod->usecount);
if (force) {
ast_log(LOG_WARNING, "Warning: Forcing removal of module '%s' with use count %d\n", resource_name, mod->usecount);
} else {
ast_log(LOG_WARNING, "Soft unload failed, '%s' has use count %d\n", resource_name, mod->usecount);
error = 1;
}
}
@ -1296,6 +1404,52 @@ exit:
return res;
}
int ast_refresh_resource(const char *resource_name, enum ast_module_unload_mode force, int recursive)
{
if (recursive) {
/* Recursively unload dependents of this module and then load them back again */
int res, i;
struct ast_vector_const_string dependents;
AST_VECTOR_INIT(&dependents, 0);
res = auto_unload_resource(resource_name, force, recursive, &dependents);
if (res) {
AST_VECTOR_FREE(&dependents);
return 1;
}
/* Start by loading the target again. */
if (ast_load_resource(resource_name)) {
ast_log(LOG_WARNING, "Failed to load module '%s' again automatically\n", resource_name);
AST_VECTOR_FREE(&dependents);
return -1;
}
res = 0;
/* Finally, load again any modules we had to unload in order to refresh the target.
* We must load modules in the reverse order that we unloaded them,
* to preserve dependency requirements. */
for (i = 0; i < AST_VECTOR_SIZE(&dependents); i++) {
const char *depname = AST_VECTOR_GET(&dependents, i);
int mres = ast_load_resource(depname);
if (mres) {
ast_log(LOG_WARNING, "Could not load module '%s' again automatically\n", depname);
}
res |= mres;
}
AST_VECTOR_FREE(&dependents);
return res ? -1 : 0;
}
/* Simple case: just unload and load the module again */
if (ast_unload_resource(resource_name, force)) {
return 1;
}
return ast_load_resource(resource_name);
}
int ast_unload_resource(const char *resource_name, enum ast_module_unload_mode force)
{
return auto_unload_resource(resource_name, force, 0, NULL);
}
static int module_matches_helper_type(struct ast_module *mod, enum ast_module_helper_type type)
{
switch (type) {

View File

@ -1112,10 +1112,18 @@
<enum name="load" />
<enum name="unload" />
<enum name="reload" />
<enum name="refresh">
<para>Completely unload and load again a specified module.</para>
</enum>
</enumlist>
<para>If no module is specified for a <literal>reload</literal> loadtype,
all modules are reloaded.</para>
</parameter>
<parameter name="Automatic" required="false">
<para>For <literal>refresh</literal> operations, attempt to automatically
unload any other modules that are dependent on this module, if that would
allow it to successfully unload, and load them again afterwards.</para>
</parameter>
</syntax>
<description>
<para>Loads, unloads or reloads an Asterisk module in a running system.</para>
@ -7172,6 +7180,7 @@ static int manager_moduleload(struct mansession *s, const struct message *m)
int res;
const char *module = astman_get_header(m, "Module");
const char *loadtype = astman_get_header(m, "LoadType");
const char *automatic = astman_get_header(m, "Automatic");
if (!loadtype || strlen(loadtype) == 0) {
astman_send_error(s, m, "Incomplete ModuleLoad action.");
@ -7194,6 +7203,13 @@ static int manager_moduleload(struct mansession *s, const struct message *m)
} else {
astman_send_ack(s, m, "Module unloaded.");
}
} else if (!strcasecmp(loadtype, "refresh")) {
res = ast_refresh_resource(module, AST_FORCE_SOFT, !ast_strlen_zero(automatic) && ast_true(automatic));
if (res) {
astman_send_error(s, m, "Could not refresh module.");
} else {
astman_send_ack(s, m, "Module unloaded and loaded.");
}
} else if (!strcasecmp(loadtype, "reload")) {
/* TODO: Unify the ack/error messages here with action_reload */
if (!ast_strlen_zero(module)) {