aboutsummaryrefslogtreecommitdiffstats
path: root/main/autoservice.c
diff options
context:
space:
mode:
authorrussell <russell@f38db490-d61c-443f-a65b-d21fe96a405b>2008-05-29 22:28:50 +0000
committerrussell <russell@f38db490-d61c-443f-a65b-d21fe96a405b>2008-05-29 22:28:50 +0000
commit74a63049357c2148109cc995363ae3ce99cb18d0 (patch)
treec8fb3312d09a31d880977562268b2a66baa34bf6 /main/autoservice.c
parent15874e67c78060dee4450322803afc71a7d863c5 (diff)
Merged revisions 119156 via svnmerge from
https://origsvn.digium.com/svn/asterisk/branches/1.4 ........ r119156 | russell | 2008-05-29 17:24:29 -0500 (Thu, 29 May 2008) | 10 lines Fix a race condition in channel autoservice. There was still a small window of opportunity for a DTMF frame, or some other deferred frame type, to come in and get dropped. (closes issue #12656) (closes issue #12656) Reported by: dimas Patches: v3-12656.patch uploaded by dimas (license 88) -- with some modifications by me ........ git-svn-id: http://svn.digium.com/svn/asterisk/trunk@119157 f38db490-d61c-443f-a65b-d21fe96a405b
Diffstat (limited to 'main/autoservice.c')
-rw-r--r--main/autoservice.c147
1 files changed, 85 insertions, 62 deletions
diff --git a/main/autoservice.c b/main/autoservice.c
index c2550916a..df475fa70 100644
--- a/main/autoservice.c
+++ b/main/autoservice.c
@@ -56,7 +56,7 @@ struct asent {
* it gets stopped for the last time. */
unsigned int use_count;
unsigned int orig_end_dtmf_flag:1;
- AST_LIST_HEAD_NOLOCK(, ast_frame) dtmf_frames;
+ AST_LIST_HEAD_NOLOCK(, ast_frame) deferred_frames;
AST_LIST_ENTRY(asent) list;
};
@@ -67,27 +67,16 @@ static pthread_t asthread = AST_PTHREADT_NULL;
static int as_chan_list_state;
-static void defer_frame(struct ast_channel *chan, struct ast_frame *f)
-{
- struct ast_frame *dup_f;
- struct asent *as;
-
- AST_LIST_LOCK(&aslist);
- AST_LIST_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_LIST_UNLOCK(&aslist);
-}
-
static void *autoservice_run(void *ign)
{
for (;;) {
- struct ast_channel *mons[MAX_AUTOMONS], *chan;
+ struct ast_channel *mons[MAX_AUTOMONS];
+ struct asent *ents[MAX_AUTOMONS];
+ struct ast_channel *chan;
struct asent *as;
- int x = 0, ms = 50;
+ int i, x = 0, ms = 50;
+ struct ast_frame *f = NULL;
+ struct ast_frame *defer_frame = NULL;
AST_LIST_LOCK(&aslist);
@@ -95,42 +84,48 @@ static void *autoservice_run(void *ign)
* to get used again. */
as_chan_list_state++;
- if (AST_LIST_EMPTY(&aslist))
+ if (AST_LIST_EMPTY(&aslist)) {
ast_cond_wait(&as_cond, &aslist.lock);
+ }
AST_LIST_TRAVERSE(&aslist, as, list) {
if (!ast_check_hangup(as->chan)) {
- if (x < MAX_AUTOMONS)
+ if (x < MAX_AUTOMONS) {
+ ents[x] = as;
mons[x++] = as->chan;
- else
+ } else {
ast_log(LOG_WARNING, "Exceeded maximum number of automatic monitoring events. Fix autoservice.c\n");
+ }
}
}
AST_LIST_UNLOCK(&aslist);
- if ((chan = ast_waitfor_n(mons, x, &ms))) {
- struct ast_frame *f = ast_read(chan);
-
- if (!f) {
- struct ast_frame hangup_frame = { 0, };
- /* No frame means the channel has been hung up.
- * A hangup frame needs to be queued here as ast_waitfor() may
- * never return again for the condition to be detected outside
- * of autoservice. So, we'll leave a HANGUP queued up so the
- * thread in charge of this channel will know. */
-
- hangup_frame.frametype = AST_FRAME_CONTROL;
- hangup_frame.subclass = AST_CONTROL_HANGUP;
+ chan = ast_waitfor_n(mons, x, &ms);
+ if (!chan) {
+ continue;
+ }
- defer_frame(chan, &hangup_frame);
+ f = ast_read(chan);
+
+ if (!f) {
+ struct ast_frame hangup_frame = { 0, };
+ /* No frame means the channel has been hung up.
+ * A hangup frame needs to be queued here as ast_waitfor() may
+ * never return again for the condition to be detected outside
+ * of autoservice. So, we'll leave a HANGUP queued up so the
+ * thread in charge of this channel will know. */
+
+ hangup_frame.frametype = AST_FRAME_CONTROL;
+ hangup_frame.subclass = AST_CONTROL_HANGUP;
+
+ defer_frame = &hangup_frame;
+ } else {
- continue;
- }
-
/* 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_END:
@@ -138,7 +133,7 @@ static void *autoservice_run(void *ign)
case AST_FRAME_TEXT:
case AST_FRAME_IMAGE:
case AST_FRAME_HTML:
- defer_frame(chan, f);
+ defer_frame = f;
break;
/* Throw these frames away */
@@ -151,9 +146,31 @@ static void *autoservice_run(void *ign)
case AST_FRAME_MODEM:
break;
}
+ }
- if (f)
+ if (!defer_frame) {
+ if (f) {
ast_frfree(f);
+ }
+ continue;
+ }
+
+ for (i = 0; i < x; i++) {
+ struct ast_frame *dup_f;
+
+ if (mons[i] != chan) {
+ continue;
+ }
+
+ if ((dup_f = ast_frdup(f))) {
+ AST_LIST_INSERT_TAIL(&ents[i]->deferred_frames, dup_f, frame_list);
+ }
+
+ break;
+ }
+
+ if (f) {
+ ast_frfree(f);
}
}
@@ -224,15 +241,10 @@ int ast_autoservice_start(struct ast_channel *chan)
int ast_autoservice_stop(struct ast_channel *chan)
{
int res = -1;
- struct asent *as;
- AST_LIST_HEAD_NOLOCK(, ast_frame) dtmf_frames;
+ struct asent *as, *removed = NULL;
struct ast_frame *f;
- int removed = 0;
- int orig_end_dtmf_flag = 0;
int chan_list_state;
- AST_LIST_HEAD_INIT_NOLOCK(&dtmf_frames);
-
AST_LIST_LOCK(&aslist);
/* Save the autoservice channel list state. We _must_ verify that the channel
@@ -241,41 +253,52 @@ int ast_autoservice_stop(struct ast_channel *chan)
* it after its gone! */
chan_list_state = as_chan_list_state;
+ /* Find the entry, but do not free it because it still can be in the
+ autoservice thread array */
AST_LIST_TRAVERSE_SAFE_BEGIN(&aslist, as, list) {
if (as->chan == chan) {
- AST_LIST_REMOVE_CURRENT(list);
as->use_count--;
- if (as->use_count)
- break;
- AST_LIST_APPEND_LIST(&dtmf_frames, &as->dtmf_frames, frame_list);
- orig_end_dtmf_flag = as->orig_end_dtmf_flag;
- ast_free(as);
- removed = 1;
- if (!ast_check_hangup(chan))
- res = 0;
+ if (as->use_count < 1) {
+ AST_LIST_REMOVE_CURRENT(list);
+ removed = as;
+ }
break;
}
}
AST_LIST_TRAVERSE_SAFE_END;
- if (removed && asthread != AST_PTHREADT_NULL)
+ if (removed && asthread != AST_PTHREADT_NULL) {
pthread_kill(asthread, SIGURG);
-
+ }
+
AST_LIST_UNLOCK(&aslist);
- if (!removed)
+ if (!removed) {
return 0;
+ }
+
+ /* Wait while autoservice thread rebuilds its list. */
+ while (chan_list_state == as_chan_list_state) {
+ usleep(1000);
+ }
+
+ /* Now autoservice thread should have no references to our entry
+ and we can safely destroy it */
+
+ if (!chan->_softhangup) {
+ res = 0;
+ }
- if (!orig_end_dtmf_flag)
+ if (!as->orig_end_dtmf_flag) {
ast_clear_flag(chan, AST_FLAG_END_DTMF_ONLY);
+ }
- while ((f = AST_LIST_REMOVE_HEAD(&dtmf_frames, frame_list))) {
+ while ((f = AST_LIST_REMOVE_HEAD(&as->deferred_frames, frame_list))) {
ast_queue_frame(chan, f);
ast_frfree(f);
}
- while (chan_list_state == as_chan_list_state)
- usleep(1000);
+ free(as);
return res;
}