res_musiconhold: Add kill_escalation_delay, kill_method to class
authorGeorge Joseph <gjoseph@digium.com>
Tue, 11 Jul 2017 12:26:27 +0000 (06:26 -0600)
committerGeorge Joseph <gjoseph@digium.com>
Tue, 11 Jul 2017 20:43:41 +0000 (14:43 -0600)
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
configs/samples/musiconhold.conf.sample
res/res_musiconhold.c

diff --git a/CHANGES b/CHANGES
index f2760c3..5daa816 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -18,6 +18,26 @@ app_queue
    been defined.
 
 ------------------------------------------------------------------------------
+--- 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 ------------
 ------------------------------------------------------------------------------
 
index b2980fc..741bde6 100644 (file)
@@ -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
index be50e9c..e4bb7a2 100644 (file)
@@ -159,6 +159,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];
@@ -179,6 +184,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 */
@@ -680,6 +689,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
@@ -755,28 +809,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 {
@@ -1357,6 +1390,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;
@@ -1610,44 +1644,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;
        }
@@ -1752,6 +1764,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")) {
@@ -1884,6 +1939,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, "<none>"));
                if (ast_test_flag(class, MOH_CUSTOM)) {
                        ast_cli(a->fd, "\tApplication: %s\n", S_OR(class->args, "<none>"));
+                       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));