Revert "autoservice: Use frame deferral API"
[asterisk/asterisk.git] / main / autoservice.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2008, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  * Russell Bryant <russell@digium.com>
8  *
9  * See http://www.asterisk.org for more information about
10  * the Asterisk project. Please do not directly contact
11  * any of the maintainers of this project for assistance;
12  * the project provides a web site, mailing lists and IRC
13  * channels for your use.
14  *
15  * This program is free software, distributed under the terms of
16  * the GNU General Public License Version 2. See the LICENSE file
17  * at the top of the source tree.
18  */
19
20 /*! \file
21  *
22  * \brief Automatic channel service routines
23  *
24  * \author Mark Spencer <markster@digium.com>
25  * \author Russell Bryant <russell@digium.com>
26  */
27
28 /*** MODULEINFO
29         <support_level>core</support_level>
30  ***/
31
32 #include "asterisk.h"
33
34 #include <sys/time.h>
35 #include <signal.h>
36
37 #include "asterisk/_private.h" /* prototype for ast_autoservice_init() */
38
39 #include "asterisk/pbx.h"
40 #include "asterisk/frame.h"
41 #include "asterisk/sched.h"
42 #include "asterisk/channel.h"
43 #include "asterisk/file.h"
44 #include "asterisk/translate.h"
45 #include "asterisk/manager.h"
46 #include "asterisk/chanvars.h"
47 #include "asterisk/linkedlists.h"
48 #include "asterisk/indications.h"
49 #include "asterisk/lock.h"
50 #include "asterisk/utils.h"
51
52 #define MAX_AUTOMONS 1500
53
54 struct asent {
55         struct ast_channel *chan;
56         /*! This gets incremented each time autoservice gets started on the same
57          *  channel.  It will ensure that it doesn't actually get stopped until
58          *  it gets stopped for the last time. */
59         unsigned int use_count;
60         unsigned int orig_end_dtmf_flag:1;
61         unsigned int ignore_frame_types;
62         /*! Frames go on at the head of deferred_frames, so we have the frames
63          *  from newest to oldest.  As we put them at the head of the readq, we'll
64          *  end up with them in the right order for the channel's readq. */
65         AST_LIST_HEAD_NOLOCK(, ast_frame) deferred_frames;
66         AST_LIST_ENTRY(asent) list;
67 };
68
69 static AST_LIST_HEAD_STATIC(aslist, asent);
70 static ast_cond_t as_cond;
71
72 static pthread_t asthread = AST_PTHREADT_NULL;
73 static volatile int asexit = 0;
74
75 static int as_chan_list_state;
76
77 static void *autoservice_run(void *ign)
78 {
79         ast_callid callid = 0;
80         struct ast_frame hangup_frame = {
81                 .frametype = AST_FRAME_CONTROL,
82                 .subclass.integer = AST_CONTROL_HANGUP,
83         };
84
85         while (!asexit) {
86                 struct ast_channel *mons[MAX_AUTOMONS];
87                 struct asent *ents[MAX_AUTOMONS];
88                 struct ast_channel *chan;
89                 struct asent *as;
90                 int i, x = 0, ms = 50;
91                 struct ast_frame *f = NULL;
92                 struct ast_frame *defer_frame = NULL;
93
94                 AST_LIST_LOCK(&aslist);
95
96                 /* At this point, we know that no channels that have been removed are going
97                  * to get used again. */
98                 as_chan_list_state++;
99
100                 if (AST_LIST_EMPTY(&aslist)) {
101                         ast_cond_wait(&as_cond, &aslist.lock);
102                 }
103
104                 AST_LIST_TRAVERSE(&aslist, as, list) {
105                         if (!ast_check_hangup(as->chan)) {
106                                 if (x < MAX_AUTOMONS) {
107                                         ents[x] = as;
108                                         mons[x++] = as->chan;
109                                 } else {
110                                         ast_log(LOG_WARNING, "Exceeded maximum number of automatic monitoring events.  Fix autoservice.c\n");
111                                 }
112                         }
113                 }
114
115                 AST_LIST_UNLOCK(&aslist);
116
117                 if (!x) {
118                         /* If we don't sleep, this becomes a busy loop, which causes
119                          * problems when Asterisk runs at a different priority than other
120                          * user processes.  As long as we check for new channels at least
121                          * once every 10ms, we should be fine. */
122                         usleep(10000);
123                         continue;
124                 }
125
126                 chan = ast_waitfor_n(mons, x, &ms);
127                 if (!chan) {
128                         continue;
129                 }
130
131                 callid = ast_channel_callid(chan);
132                 ast_callid_threadassoc_change(callid);
133
134                 f = ast_read(chan);
135
136                 if (!f) {
137                         /* No frame means the channel has been hung up.
138                          * A hangup frame needs to be queued here as ast_waitfor() may
139                          * never return again for the condition to be detected outside
140                          * of autoservice.  So, we'll leave a HANGUP queued up so the
141                          * thread in charge of this channel will know. */
142
143                         defer_frame = &hangup_frame;
144                 } else if (ast_is_deferrable_frame(f)) {
145                         defer_frame = f;
146                 } else {
147                         /* Can't defer. Discard and continue with next. */
148                         ast_frfree(f);
149                         continue;
150                 }
151
152                 for (i = 0; i < x; i++) {
153                         struct ast_frame *dup_f;
154
155                         if (mons[i] != chan) {
156                                 continue;
157                         }
158
159                         if (!f) { /* defer_frame == &hangup_frame */
160                                 if ((dup_f = ast_frdup(defer_frame))) {
161                                         AST_LIST_INSERT_HEAD(&ents[i]->deferred_frames, dup_f, frame_list);
162                                 }
163                         } else {
164                                 if ((dup_f = ast_frisolate(defer_frame))) {
165                                         AST_LIST_INSERT_HEAD(&ents[i]->deferred_frames, dup_f, frame_list);
166                                 }
167                                 if (dup_f != defer_frame) {
168                                         ast_frfree(defer_frame);
169                                 }
170                         }
171
172                         break;
173                 }
174                 /* The ast_waitfor_n() call will only read frames from
175                  * the channels' file descriptors. If ast_waitfor_n()
176                  * returns non-NULL, then one of the channels in the
177                  * mons array must have triggered the return. It's
178                  * therefore impossible that we got here while (i >= x).
179                  * If we did, we'd need to ast_frfree(f) if (f). */
180         }
181
182         ast_callid_threadassoc_change(0);
183         asthread = AST_PTHREADT_NULL;
184
185         return NULL;
186 }
187
188 int ast_autoservice_start(struct ast_channel *chan)
189 {
190         int res = 0;
191         struct asent *as;
192
193         AST_LIST_LOCK(&aslist);
194         AST_LIST_TRAVERSE(&aslist, as, list) {
195                 if (as->chan == chan) {
196                         as->use_count++;
197                         break;
198                 }
199         }
200         AST_LIST_UNLOCK(&aslist);
201
202         if (as) {
203                 /* Entry exists, autoservice is already handling this channel */
204                 return 0;
205         }
206
207         if (!(as = ast_calloc(1, sizeof(*as))))
208                 return -1;
209
210         /* New entry created */
211         as->chan = chan;
212         as->use_count = 1;
213
214         ast_channel_lock(chan);
215         as->orig_end_dtmf_flag = ast_test_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY) ? 1 : 0;
216         if (!as->orig_end_dtmf_flag)
217                 ast_set_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY);
218         ast_channel_unlock(chan);
219
220         AST_LIST_LOCK(&aslist);
221
222         if (AST_LIST_EMPTY(&aslist) && asthread != AST_PTHREADT_NULL) {
223                 ast_cond_signal(&as_cond);
224         }
225
226         AST_LIST_INSERT_HEAD(&aslist, as, list);
227
228         if (asthread == AST_PTHREADT_NULL) { /* need start the thread */
229                 if (ast_pthread_create_background(&asthread, NULL, autoservice_run, NULL)) {
230                         ast_log(LOG_WARNING, "Unable to create autoservice thread :(\n");
231                         /* There will only be a single member in the list at this point,
232                            the one we just added. */
233                         AST_LIST_REMOVE(&aslist, as, list);
234                         ast_free(as);
235                         asthread = AST_PTHREADT_NULL;
236                         res = -1;
237                 } else {
238                         pthread_kill(asthread, SIGURG);
239                 }
240         }
241
242         AST_LIST_UNLOCK(&aslist);
243
244         return res;
245 }
246
247 int ast_autoservice_stop(struct ast_channel *chan)
248 {
249         int res = -1;
250         struct asent *as, *removed = NULL;
251         struct ast_frame *f;
252         int chan_list_state;
253
254         AST_LIST_LOCK(&aslist);
255
256         /* Save the autoservice channel list state.  We _must_ verify that the channel
257          * list has been rebuilt before we return.  Because, after we return, the channel
258          * could get destroyed and we don't want our poor autoservice thread to step on
259          * it after its gone! */
260         chan_list_state = as_chan_list_state;
261
262         /* Find the entry, but do not free it because it still can be in the
263            autoservice thread array */
264         AST_LIST_TRAVERSE_SAFE_BEGIN(&aslist, as, list) {
265                 if (as->chan == chan) {
266                         as->use_count--;
267                         if (as->use_count < 1) {
268                                 AST_LIST_REMOVE_CURRENT(list);
269                                 removed = as;
270                         }
271                         break;
272                 }
273         }
274         AST_LIST_TRAVERSE_SAFE_END;
275
276         if (removed && asthread != AST_PTHREADT_NULL) {
277                 pthread_kill(asthread, SIGURG);
278         }
279
280         AST_LIST_UNLOCK(&aslist);
281
282         if (!removed) {
283                 return 0;
284         }
285
286         /* Wait while autoservice thread rebuilds its list. */
287         while (chan_list_state == as_chan_list_state) {
288                 usleep(1000);
289         }
290
291         /* Now autoservice thread should have no references to our entry
292            and we can safely destroy it */
293
294         if (!ast_channel_softhangup_internal_flag(chan)) {
295                 res = 0;
296         }
297
298         if (!as->orig_end_dtmf_flag) {
299                 ast_clear_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY);
300         }
301
302         ast_channel_lock(chan);
303         while ((f = AST_LIST_REMOVE_HEAD(&as->deferred_frames, frame_list))) {
304                 if (!((1 << f->frametype) & as->ignore_frame_types)) {
305                         ast_queue_frame_head(chan, f);
306                 }
307                 ast_frfree(f);
308         }
309         ast_channel_unlock(chan);
310
311         ast_free(as);
312
313         return res;
314 }
315
316 void ast_autoservice_chan_hangup_peer(struct ast_channel *chan, struct ast_channel *peer)
317 {
318         if (chan && !ast_autoservice_start(chan)) {
319                 ast_hangup(peer);
320                 ast_autoservice_stop(chan);
321         } else {
322                 ast_hangup(peer);
323         }
324 }
325
326 int ast_autoservice_ignore(struct ast_channel *chan, enum ast_frame_type ftype)
327 {
328         struct asent *as;
329         int res = -1;
330
331         AST_LIST_LOCK(&aslist);
332         AST_LIST_TRAVERSE(&aslist, as, list) {
333                 if (as->chan == chan) {
334                         res = 0;
335                         as->ignore_frame_types |= (1 << ftype);
336                         break;
337                 }
338         }
339         AST_LIST_UNLOCK(&aslist);
340         return res;
341 }
342
343 static void autoservice_shutdown(void)
344 {
345         pthread_t th = asthread;
346         asexit = 1;
347         if (th != AST_PTHREADT_NULL) {
348                 ast_cond_signal(&as_cond);
349                 pthread_kill(th, SIGURG);
350                 pthread_join(th, NULL);
351         }
352 }
353
354 void ast_autoservice_init(void)
355 {
356         ast_register_cleanup(autoservice_shutdown);
357         ast_cond_init(&as_cond, NULL);
358 }