From 9bd8812337f87b86ccecfc29a4916a93af9e693b Mon Sep 17 00:00:00 2001 From: George Joseph Date: Tue, 11 Jul 2017 06:26:27 -0600 Subject: [PATCH] res_musiconhold: Add kill_escalation_delay, kill_method to class By default, when res_musiconhold reloads or unloads, it sends a HUP signal to custom applications (and all descendants), waits 100ms, then sends a TERM signal, waits 100ms, then finally sends a KILL signal. An application which is interacting with an external device and/or spawns children of its own may not be able to exit cleanly in the default times, expecially if sent a KILL signal, or if it's children are getting signals directly from res_musiconhoild. * To allow extra time, the 'kill_escalation_delay' class option can be used to set the number of milliseconds res_musiconhold waits before escalating kill signals, with the default being the current 100ms. * To control to whom the signals are sent, the "kill_method" class option can be set to "process_group" (the default, existing behavior), which sends signals to the application and its descendants directly, or "process" which sends signals only to the application itself. Change-Id: Iff70a1a9405685a9021a68416830c0db5158603b --- CHANGES | 20 +++ configs/samples/musiconhold.conf.sample | 23 ++++ res/res_musiconhold.c | 156 ++++++++++++++++-------- 3 files changed, 150 insertions(+), 49 deletions(-) diff --git a/CHANGES b/CHANGES index 9d6cea08c4..5907e13d23 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,26 @@ === ============================================================================== +------------------------------------------------------------------------------ +--- Functionality changes from Asterisk 14.6.0 to Asterisk 14.7.0 ------------ +------------------------------------------------------------------------------ + +res_musiconhold +------------------ + * By default, when res_musiconhold reloads or unloads, it sends a HUP signal + to custom applications (and all descendants), waits 100ms, then sends a + TERM signal, waits 100ms, then finally sends a KILL signal. An application + which is interacting with an external device and/or spawns children of its + own may not be able to exit cleanly in the default times, expecially if sent + a KILL signal, or if it's children are getting signals directly from + res_musiconhoild. To allow extra time, the 'kill_escalation_delay' + class option can be used to set the number of milliseconds res_musiconhold + waits before escalating kill signals, with the default being the current + 100ms. To control to whom the signals are sent, the "kill_method" + class option can be set to "process_group" (the default, existing behavior), + which sends signals to the application and its descendants directly, or + "process" which sends signals only to the application itself. + ------------------------------------------------------------------------------ --- Functionality changes from Asterisk 14.5.0 to Asterisk 14.6.0 ------------ ------------------------------------------------------------------------------ diff --git a/configs/samples/musiconhold.conf.sample b/configs/samples/musiconhold.conf.sample index b2980fc1de..741bde6037 100644 --- a/configs/samples/musiconhold.conf.sample +++ b/configs/samples/musiconhold.conf.sample @@ -97,3 +97,26 @@ directory=moh ;mode=custom ;directory=/var/lib/asterisk/mohmp3 ;application=/site/sw/bin/madplay -Q -o raw:- --mono -R 8000 -a -12 + +; By default, when res_musiconhold reloads or unloads, it sends a HUP signal +; to custom applications (and all descendants), waits 100ms, then sends a +; TERM signal, waits 100ms, then finally sends a KILL signal. An application +; which is interacting with an external device and/or spawns children of its +; own may not be able to exit cleanly in the default times, expecially if sent +; a KILL signal, or if it's children are getting signals directly from +; res_musiconhoild. To allow extra time, the 'kill_escalation_delay' +; class option can be used to set the number of milliseconds res_musiconhold +; waits before escalating kill signals, with the default being the current +; 100ms. To control to whom the signals are sent, the "kill_method" +; class option can be set to "process_group" (the default, existing behavior), +; which sends signals to the application and its descendants directly, or +; "process" which sends signals only to the application itself. + +;[sox_from_device] +;mode=custom +;directory=/var/lib/asterisk/mohmp3 +;application=/usr/bin/sox -q -t alsa -c 2 -r 48000 hw:1 -c 1 -r 8000 -t raw -s - +; Wait 500ms before escalating kill signals +;kill_escalation_delay=500 +; Send signals to just the child process instead of all descendants +;kill_method=process diff --git a/res/res_musiconhold.c b/res/res_musiconhold.c index a070f31fd7..9c995bf251 100644 --- a/res/res_musiconhold.c +++ b/res/res_musiconhold.c @@ -161,6 +161,11 @@ struct moh_files_state { static struct ast_flags global_flags[1] = {{0}}; /*!< global MOH_ flags */ +enum kill_methods { + KILL_METHOD_PROCESS_GROUP = 0, + KILL_METHOD_PROCESS +}; + struct mohclass { char name[MAX_MUSICCLASS]; char dir[256]; @@ -181,6 +186,10 @@ struct mohclass { int pid; time_t start; pthread_t thread; + /*! Millisecond delay between kill attempts */ + size_t kill_delay; + /*! Kill method */ + enum kill_methods kill_method; /*! Source of audio */ int srcfd; /*! Generic timer */ @@ -682,6 +691,51 @@ static int spawn_mp3(struct mohclass *class) return fds[0]; } +static int killer(pid_t pid, int signum, enum kill_methods kill_method) +{ + switch (kill_method) { + case KILL_METHOD_PROCESS_GROUP: + return killpg(pid, signum); + case KILL_METHOD_PROCESS: + return kill(pid, signum); + } + + return -1; +} + +static void killpid(int pid, size_t delay, enum kill_methods kill_method) +{ + if (killer(pid, SIGHUP, kill_method) < 0) { + if (errno == ESRCH) { + return; + } + ast_log(LOG_WARNING, "Unable to send a SIGHUP to MOH process '%d'?!!: %s\n", pid, strerror(errno)); + } else { + ast_debug(1, "Sent HUP to pid %d%s\n", pid, + kill_method == KILL_METHOD_PROCESS_GROUP ? " and all children" : " only"); + } + usleep(delay); + if (killer(pid, SIGTERM, kill_method) < 0) { + if (errno == ESRCH) { + return; + } + ast_log(LOG_WARNING, "Unable to terminate MOH process '%d'?!!: %s\n", pid, strerror(errno)); + } else { + ast_debug(1, "Sent TERM to pid %d%s\n", pid, + kill_method == KILL_METHOD_PROCESS_GROUP ? " and all children" : " only"); + } + usleep(delay); + if (killer(pid, SIGKILL, kill_method) < 0) { + if (errno == ESRCH) { + return; + } + ast_log(LOG_WARNING, "Unable to kill MOH process '%d'?!!: %s\n", pid, strerror(errno)); + } else { + ast_debug(1, "Sent KILL to pid %d%s\n", pid, + kill_method == KILL_METHOD_PROCESS_GROUP ? " and all children" : " only"); + } +} + static void *monmp3thread(void *data) { #define MOH_MS_INTERVAL 100 @@ -757,28 +811,7 @@ static void *monmp3thread(void *data) class->srcfd = -1; pthread_testcancel(); if (class->pid > 1) { - do { - if (killpg(class->pid, SIGHUP) < 0) { - if (errno == ESRCH) { - break; - } - ast_log(LOG_WARNING, "Unable to send a SIGHUP to MOH process?!!: %s\n", strerror(errno)); - } - usleep(100000); - if (killpg(class->pid, SIGTERM) < 0) { - if (errno == ESRCH) { - break; - } - ast_log(LOG_WARNING, "Unable to terminate MOH process?!!: %s\n", strerror(errno)); - } - usleep(100000); - if (killpg(class->pid, SIGKILL) < 0) { - if (errno == ESRCH) { - break; - } - ast_log(LOG_WARNING, "Unable to kill MOH process?!!: %s\n", strerror(errno)); - } - } while (0); + killpid(class->pid, class->kill_delay, class->kill_method); class->pid = 0; } } else { @@ -1359,6 +1392,7 @@ static struct mohclass *_moh_class_malloc(const char *file, int line, const char if (class) { class->format = ao2_bump(ast_format_slin); class->srcfd = -1; + class->kill_delay = 100000; } return class; @@ -1612,44 +1646,22 @@ static void moh_class_destructor(void *obj) if (class->pid > 1) { char buff[8192]; - int bytes, tbytes = 0, stime = 0, pid = 0; + int bytes, tbytes = 0, stime = 0; ast_debug(1, "killing %d!\n", class->pid); stime = time(NULL) + 2; - pid = class->pid; - class->pid = 0; - - /* Back when this was just mpg123, SIGKILL was fine. Now we need - * to give the process a reason and time enough to kill off its - * children. */ - do { - if (killpg(pid, SIGHUP) < 0) { - ast_log(LOG_WARNING, "Unable to send a SIGHUP to MOH process?!!: %s\n", strerror(errno)); - } - usleep(100000); - if (killpg(pid, SIGTERM) < 0) { - if (errno == ESRCH) { - break; - } - ast_log(LOG_WARNING, "Unable to terminate MOH process?!!: %s\n", strerror(errno)); - } - usleep(100000); - if (killpg(pid, SIGKILL) < 0) { - if (errno == ESRCH) { - break; - } - ast_log(LOG_WARNING, "Unable to kill MOH process?!!: %s\n", strerror(errno)); - } - } while (0); + killpid(class->pid, class->kill_delay, class->kill_method); while ((ast_wait_for_input(class->srcfd, 100) > 0) && (bytes = read(class->srcfd, buff, 8192)) && time(NULL) < stime) { tbytes = tbytes + bytes; } - ast_debug(1, "mpg123 pid %d and child died after %d bytes read\n", pid, tbytes); + ast_debug(1, "mpg123 pid %d and child died after %d bytes read\n", + class->pid, tbytes); + class->pid = 0; close(class->srcfd); class->srcfd = -1; } @@ -1754,6 +1766,49 @@ static int load_moh_classes(int reload) /* For compatibility with the past, we overwrite any name=name * with the context [name]. */ ast_copy_string(class->name, cat, sizeof(class->name)); + for (var = ast_variable_browse(cfg, cat); var; var = var->next) { + if (!strcasecmp(var->name, "mode")) { + ast_copy_string(class->mode, var->value, sizeof(class->mode)); + } else if (!strcasecmp(var->name, "directory")) { + ast_copy_string(class->dir, var->value, sizeof(class->dir)); + } else if (!strcasecmp(var->name, "application")) { + ast_copy_string(class->args, var->value, sizeof(class->args)); + } else if (!strcasecmp(var->name, "announcement")) { + ast_copy_string(class->announcement, var->value, sizeof(class->announcement)); + ast_set_flag(class, MOH_ANNOUNCEMENT); + } else if (!strcasecmp(var->name, "digit") && (isdigit(*var->value) || strchr("*#", *var->value))) { + class->digit = *var->value; + } else if (!strcasecmp(var->name, "random")) { + ast_set2_flag(class, ast_true(var->value), MOH_RANDOMIZE); + } else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "random")) { + ast_set_flag(class, MOH_RANDOMIZE); + } else if (!strcasecmp(var->name, "sort") && !strcasecmp(var->value, "alpha")) { + ast_set_flag(class, MOH_SORTALPHA); + } else if (!strcasecmp(var->name, "format")) { + ao2_cleanup(class->format); + class->format = ast_format_cache_get(var->value); + if (!class->format) { + ast_log(LOG_WARNING, "Unknown format '%s' -- defaulting to SLIN\n", var->value); + class->format = ao2_bump(ast_format_slin); + } + } else if (!strcasecmp(var->name, "kill_escalation_delay")) { + if (sscanf(var->value, "%zu", &class->kill_delay) == 1) { + class->kill_delay *= 1000; + } else { + ast_log(LOG_WARNING, "kill_escalation_delay '%s' is invalid. Setting to 100ms\n", var->value); + class->kill_delay = 100000; + } + } else if (!strcasecmp(var->name, "kill_method")) { + if (!strcasecmp(var->value, "process")) { + class->kill_method = KILL_METHOD_PROCESS; + } else if (!strcasecmp(var->value, "process_group")){ + class->kill_method = KILL_METHOD_PROCESS_GROUP; + } else { + ast_log(LOG_WARNING, "kill_method '%s' is invalid. Setting to 'process_group'\n", var->value); + class->kill_method = KILL_METHOD_PROCESS_GROUP; + } + } + } if (ast_strlen_zero(class->dir)) { if (!strcasecmp(class->mode, "custom")) { @@ -1886,6 +1941,9 @@ static char *handle_cli_moh_show_classes(struct ast_cli_entry *e, int cmd, struc ast_cli(a->fd, "\tDirectory: %s\n", S_OR(class->dir, "")); if (ast_test_flag(class, MOH_CUSTOM)) { ast_cli(a->fd, "\tApplication: %s\n", S_OR(class->args, "")); + ast_cli(a->fd, "\tKill Escalation Delay: %zu ms\n", class->kill_delay / 1000); + ast_cli(a->fd, "\tKill Method: %s\n", + class->kill_method == KILL_METHOD_PROCESS ? "process" : "process_group"); } if (strcasecmp(class->mode, "files")) { ast_cli(a->fd, "\tFormat: %s\n", ast_format_get_name(class->format));