Merged revisions 89790 via svnmerge from
authorRussell Bryant <russell@russellbryant.com>
Tue, 27 Nov 2007 22:05:36 +0000 (22:05 +0000)
committerRussell Bryant <russell@russellbryant.com>
Tue, 27 Nov 2007 22:05:36 +0000 (22:05 +0000)
https://origsvn.digium.com/svn/asterisk/branches/1.4

........
r89790 | russell | 2007-11-27 15:45:51 -0600 (Tue, 27 Nov 2007) | 41 lines

Merge changes from team/russell/autoservice_1.4

This set of changes fixes an issue that was reported to me on IRC yesterday.
The user, d1mas, was using chan_zap for incoming calls and was having DTMF
recognition issues in some situations.  Specifically, he noticed that the
problem occurred when using DISA or WaitExten.  He also noticed that when
using Read, the problem did not occur.  His system also used DUNDi for
dialplan lookups.

So, he theorized that if the DUNDi lookups blocked for some period of time,
that audio from the zap channel could get lost.  If the audio got lost, then
it wouldn't be run through the DTMF detector, and digits could get lost.
He was correct, and the following set of changes fixes the problem.  However,
the changes go a little bit further than what was necessary to fix this exact
problem.

1) I updated pbx_extension_helper() to autoservice the associated channel to
   handle cases where extension lookups may take a long time.  This would
   normally be a dialplan switch that does some lookup over the network, such
   as the DUNDi or IAX2 switches.

   This ensures that even while a DUNDi lookup is blocking, the channel will be
   continuously serviced.

2) I made a change to the autoservice code.  This is actually something that
   has bothered me for a long time.  When a channel is in autoservice, _all_
   frames get thrown away.  However, some frames really shouldn't be thrown
   away.  The most notable examples are signalling (CONTROL) frames, and DTMF.

   So, this patch queues up important frames while a channel is in autoservice.
   When autoservice is stopped on the channel, the queued up frames get stuck
   back on the channel so that they can get processed instead of thrown away.

3) I made another change to the autoservice code to handle the case where
   autoservice is started on channels recursively.

   Previously, you could call ast_autoservice_start() multiple times on a
   channel, and it would stop the first time ast_autoservice_stop() gets
   called.  Now, it will ensure that autoservice doesn't actually stop until
   the final call to ast_autoservice_stop().

........

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@89791 65c4cc65-6c06-0410-ace0-fbb531ad65f3

main/autoservice.c
main/pbx.c

index 39bad2f..2622e95 100644 (file)
@@ -47,6 +47,11 @@ ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
 
 struct asent {
        struct ast_channel *chan;
+       /*! This gets incremented each time autoservice gets started on the same
+        *  channel.  It will ensure that it doesn't actually get stopped until 
+        *  it gets stopped for the last time. */
+       unsigned int use_count;
+       AST_LIST_HEAD_NOLOCK(, ast_frame) dtmf_frames;
        AST_LIST_ENTRY(asent) list;
 };
 
@@ -74,8 +79,42 @@ static void *autoservice_run(void *ign)
                AST_RWLIST_UNLOCK(&aslist);
 
                if ((chan = ast_waitfor_n(mons, x, &ms))) {
-                       /* Read and ignore anything that occurs */
                        struct ast_frame *f = ast_read(chan);
+                       
+                       /* Do not add a default entry in this switch statement.  Each new
+                        * frame type should be addressed directly as to whether it should
+                        * be queued up or not. */
+                       switch (f->frametype) {
+                       /* Save these frames */
+                       case AST_FRAME_DTMF_BEGIN:
+                       case AST_FRAME_DTMF_END:
+                       case AST_FRAME_CONTROL:
+                       case AST_FRAME_TEXT:
+                       case AST_FRAME_IMAGE:
+                       case AST_FRAME_HTML:
+                       {
+                               struct ast_frame *dup_f;
+
+                               AST_RWLIST_WRLOCK(&aslist);
+                               AST_RWLIST_TRAVERSE(&aslist, as, list) {
+                                       if (as->chan != chan)
+                                               continue;
+                                       if ((dup_f = ast_frdup(f)))
+                                               AST_LIST_INSERT_TAIL(&as->dtmf_frames, dup_f, frame_list);
+                               }
+                               AST_RWLIST_UNLOCK(&aslist);
+                       }
+
+                       /* Throw these frames away */
+                       case AST_FRAME_VOICE:
+                       case AST_FRAME_VIDEO:
+                       case AST_FRAME_NULL:
+                       case AST_FRAME_IAX:
+                       case AST_FRAME_CNG:
+                       case AST_FRAME_MODEM:
+                               break;
+                       }
+
                        if (f)
                                ast_frfree(f);
                }
@@ -95,13 +134,16 @@ int ast_autoservice_start(struct ast_channel *chan)
 
        /* Check if the channel already has autoservice */
        AST_RWLIST_TRAVERSE(&aslist, as, list) {
-               if (as->chan == chan)
+               if (as->chan == chan) {
+                       as->use_count++;
                        break;
+               }
        }
 
        /* If not, start autoservice on channel */
        if (!as && (as = ast_calloc(1, sizeof(*as)))) {
                as->chan = chan;
+               as->use_count = 1;
                AST_RWLIST_INSERT_HEAD(&aslist, as, list);
                res = 0;
                if (asthread == AST_PTHREADT_NULL) { /* need start the thread */
@@ -116,7 +158,9 @@ int ast_autoservice_start(struct ast_channel *chan)
                                pthread_kill(asthread, SIGURG);
                }
        }
+
        AST_RWLIST_UNLOCK(&aslist);
+
        return res;
 }
 
@@ -124,11 +168,22 @@ int ast_autoservice_stop(struct ast_channel *chan)
 {
        int res = -1;
        struct asent *as;
+       AST_LIST_HEAD_NOLOCK(, ast_frame) dtmf_frames;
+       struct ast_frame *f;
+       int removed = 1;
+
+       AST_LIST_HEAD_INIT_NOLOCK(&dtmf_frames);
 
        AST_RWLIST_WRLOCK(&aslist);
        AST_RWLIST_TRAVERSE_SAFE_BEGIN(&aslist, as, list) {     
                if (as->chan == chan) {
                        AST_RWLIST_REMOVE_CURRENT(list);
+                       as->use_count--;
+                       if (as->use_count) {
+                               removed = 0;
+                               break;
+                       }
+                       AST_LIST_APPEND_LIST(&dtmf_frames, &as->dtmf_frames, frame_list);
                        ast_free(as);
                        if (!ast_check_hangup(chan))
                                res = 0;
@@ -137,13 +192,22 @@ int ast_autoservice_stop(struct ast_channel *chan)
        }
        AST_RWLIST_TRAVERSE_SAFE_END;
 
-       if (asthread != AST_PTHREADT_NULL) 
+       if (removed && asthread != AST_PTHREADT_NULL) 
                pthread_kill(asthread, SIGURG);
+       
        AST_RWLIST_UNLOCK(&aslist);
 
+       if (!removed)
+               return 0;
+
        /* Wait for it to un-block */
        while (ast_test_flag(chan, AST_FLAG_BLOCKING))
                usleep(1000);
 
+       while ((f = AST_LIST_REMOVE_HEAD(&dtmf_frames, frame_list))) {
+               ast_queue_frame(chan, f);
+               ast_frfree(f);
+       }
+
        return res;
 }
index 5ab13ba..c23b211 100644 (file)
@@ -2579,7 +2579,8 @@ static void pbx_substitute_variables(char *passdata, int datalen, struct ast_cha
        pbx_substitute_variables_helper(c, e->data, passdata, datalen - 1);
 }
 
-/*! \brief The return value depends on the action:
+/*! 
+ * \brief The return value depends on the action:
  *
  * E_MATCH, E_CANMATCH, E_MATCHMORE require a real match,
  *     and return 0 on failure, -1 on match;
@@ -2589,6 +2590,12 @@ static void pbx_substitute_variables(char *passdata, int datalen, struct ast_cha
  *     
  * \retval 0 on success.
  * \retval  -1 on failure.
+ *
+ * \note The channel is auto-serviced in this function, because doing an extension
+ * match may block for a long time.  For example, if the lookup has to use a network
+ * dialplan switch, such as DUNDi or IAX2, it may take a while.  However, the channel
+ * auto-service code will queue up any important signalling frames to be processed
+ * after this is done.
  */
 static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con,
   const char *context, const char *exten, int priority,
@@ -2601,20 +2608,25 @@ static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con,
        char passdata[EXT_DATA_SIZE];
 
        int matching_action = (action == E_MATCH || action == E_CANMATCH || action == E_MATCHMORE);
+
+       ast_autoservice_start(c);
        
        ast_rdlock_contexts();
        if (found)
                *found = 0;
+
        e = pbx_find_extension(c, con, &q, context, exten, priority, label, callerid, action);
        if (e) {
                if (found)
                        *found = 1;
                if (matching_action) {
                        ast_unlock_contexts();
+                       ast_autoservice_stop(c);
                        return -1;      /* success, we found it */
                } else if (action == E_FINDLABEL) { /* map the label to a priority */
                        res = e->priority;
                        ast_unlock_contexts();
+                       ast_autoservice_stop(c);
                        return res;     /* the priority we were looking for */
                } else {        /* spawn */
                        if (!e->cached_app)
@@ -2623,6 +2635,7 @@ static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con,
                        ast_unlock_contexts();
                        if (!app) {
                                ast_log(LOG_WARNING, "No application '%s' for extension (%s, %s, %d)\n", e->app, context, exten, priority);
+                               ast_autoservice_stop(c);
                                return -1;
                        }
                        if (c->context != context)
@@ -2658,17 +2671,20 @@ static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con,
                                        "AppData: %s\r\n"
                                        "Uniqueid: %s\r\n",
                                        c->name, c->context, c->exten, c->priority, app->name, passdata, c->uniqueid);
+                       ast_autoservice_stop(c);
                        return pbx_exec(c, app, passdata);      /* 0 on success, -1 on failure */
                }
        } else if (q.swo) {     /* not found here, but in another switch */
                ast_unlock_contexts();
-               if (matching_action)
+               if (matching_action) {
+                       ast_autoservice_stop(c);
                        return -1;
-               else {
+               } else {
                        if (!q.swo->exec) {
                                ast_log(LOG_WARNING, "No execution engine for switch %s\n", q.swo->name);
                                res = -1;
                        }
+                       ast_autoservice_stop(c);
                        return q.swo->exec(c, q.foundcontext ? q.foundcontext : context, exten, priority, callerid, q.data);
                }
        } else {        /* not found anywhere, see what happened */
@@ -2694,6 +2710,8 @@ static int pbx_extension_helper(struct ast_channel *c, struct ast_context *con,
                        ast_debug(1, "Shouldn't happen!\n");
                }
 
+               ast_autoservice_stop(c);
+
                return (matching_action) ? 0 : -1;
        }
 }