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: https://origsvn.digium.com/svn/asterisk/trunk@119157 65c4cc65-6c06-0410-ace0-fbb531ad65f3
This commit is contained in:
Russell Bryant 2008-05-29 22:28:50 +00:00
parent 5b8e1963c5
commit f98e98819e
1 changed files with 83 additions and 60 deletions

View File

@ -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);
chan = ast_waitfor_n(mons, x, &ms);
if (!chan) {
continue;
}
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. */
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;
hangup_frame.frametype = AST_FRAME_CONTROL;
hangup_frame.subclass = AST_CONTROL_HANGUP;
defer_frame(chan, &hangup_frame);
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;
}
if (!orig_end_dtmf_flag)
/* 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 (!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;
}