diff --git a/main/autoservice.c b/main/autoservice.c index c8a59a63906d810ed07278505caddacfd8408326..11c9eab969d85c90346bb9254c14f94eefa900d7 100644 --- a/main/autoservice.c +++ b/main/autoservice.c @@ -59,6 +59,10 @@ struct asent { unsigned int use_count; unsigned int orig_end_dtmf_flag:1; unsigned int ignore_frame_types; + /*! Frames go on at the head of deferred_frames, so we have the frames + * from newest to oldest. As we put them at the head of the readq, we'll + * end up with them in the right order for the channel's readq. */ + AST_LIST_HEAD_NOLOCK(, ast_frame) deferred_frames; AST_LIST_ENTRY(asent) list; }; @@ -73,13 +77,19 @@ static int as_chan_list_state; static void *autoservice_run(void *ign) { ast_callid callid = 0; + struct ast_frame hangup_frame = { + .frametype = AST_FRAME_CONTROL, + .subclass.integer = AST_CONTROL_HANGUP, + }; while (!asexit) { 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); @@ -94,6 +104,7 @@ static void *autoservice_run(void *ign) AST_LIST_TRAVERSE(&aslist, as, list) { if (!ast_check_hangup(as->chan)) { if (x < MAX_AUTOMONS) { + ents[x] = as; mons[x++] = as->chan; } else { ast_log(LOG_WARNING, "Exceeded maximum number of automatic monitoring events. Fix autoservice.c\n"); @@ -121,9 +132,51 @@ static void *autoservice_run(void *ign) ast_callid_threadassoc_change(callid); f = ast_read(chan); - if (f) { + + if (!f) { + /* 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. */ + + defer_frame = &hangup_frame; + } else if (ast_is_deferrable_frame(f)) { + defer_frame = f; + } else { + /* Can't defer. Discard and continue with next. */ ast_frfree(f); + continue; } + + for (i = 0; i < x; i++) { + struct ast_frame *dup_f; + + if (mons[i] != chan) { + continue; + } + + if (!f) { /* defer_frame == &hangup_frame */ + if ((dup_f = ast_frdup(defer_frame))) { + AST_LIST_INSERT_HEAD(&ents[i]->deferred_frames, dup_f, frame_list); + } + } else { + if ((dup_f = ast_frisolate(defer_frame))) { + AST_LIST_INSERT_HEAD(&ents[i]->deferred_frames, dup_f, frame_list); + } + if (dup_f != defer_frame) { + ast_frfree(defer_frame); + } + } + + break; + } + /* The ast_waitfor_n() call will only read frames from + * the channels' file descriptors. If ast_waitfor_n() + * returns non-NULL, then one of the channels in the + * mons array must have triggered the return. It's + * therefore impossible that we got here while (i >= x). + * If we did, we'd need to ast_frfree(f) if (f). */ } ast_callid_threadassoc_change(0); @@ -162,7 +215,6 @@ int ast_autoservice_start(struct ast_channel *chan) as->orig_end_dtmf_flag = ast_test_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY) ? 1 : 0; if (!as->orig_end_dtmf_flag) ast_set_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY); - ast_channel_start_defer_frames(chan); ast_channel_unlock(chan); AST_LIST_LOCK(&aslist); @@ -196,6 +248,7 @@ int ast_autoservice_stop(struct ast_channel *chan) { int res = -1; struct asent *as, *removed = NULL; + struct ast_frame *f; int chan_list_state; AST_LIST_LOCK(&aslist); @@ -247,7 +300,12 @@ int ast_autoservice_stop(struct ast_channel *chan) } ast_channel_lock(chan); - ast_channel_stop_defer_frames(chan); + while ((f = AST_LIST_REMOVE_HEAD(&as->deferred_frames, frame_list))) { + if (!((1 << f->frametype) & as->ignore_frame_types)) { + ast_queue_frame_head(chan, f); + } + ast_frfree(f); + } ast_channel_unlock(chan); ast_free(as);