Merge ace9d3431d
into 555a541680
This commit is contained in:
commit
872ca2c0cf
|
@ -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.
|
||||
|
|
29
main/cli.c
29
main/cli.c
|
@ -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]);
|
||||
|
|
168
main/loader.c
168
main/loader.c
|
@ -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) {
|
||||
|
|
|
@ -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)) {
|
||||
|
|
Loading…
Reference in New Issue