Revert "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         AST_LIST_ENTRY(asent) list;
63 };
64
65 static AST_LIST_HEAD_STATIC(aslist, asent);
66 static ast_cond_t as_cond;
67
68 static pthread_t asthread = AST_PTHREADT_NULL;
69 static volatile int asexit = 0;
70
71 static int as_chan_list_state;
72
73 static void *autoservice_run(void *ign)
74 {
75         ast_callid callid = 0;
76
77         while (!asexit) {
78                 struct ast_channel *mons[MAX_AUTOMONS];
79                 struct ast_channel *chan;
80                 struct asent *as;
81                 int x = 0, ms = 50;
82                 struct ast_frame *f = NULL;
83
84                 AST_LIST_LOCK(&aslist);
85
86                 /* At this point, we know that no channels that have been removed are going
87                  * to get used again. */
88                 as_chan_list_state++;
89
90                 if (AST_LIST_EMPTY(&aslist)) {
91                         ast_cond_wait(&as_cond, &aslist.lock);
92                 }
93
94                 AST_LIST_TRAVERSE(&aslist, as, list) {
95                         if (!ast_check_hangup(as->chan)) {
96                                 if (x < MAX_AUTOMONS) {
97                                         mons[x++] = as->chan;
98                                 } else {
99                                         ast_log(LOG_WARNING, "Exceeded maximum number of automatic monitoring events.  Fix autoservice.c\n");
100                                 }
101                         }
102                 }
103
104                 AST_LIST_UNLOCK(&aslist);
105
106                 if (!x) {
107                         /* If we don't sleep, this becomes a busy loop, which causes
108                          * problems when Asterisk runs at a different priority than other
109                          * user processes.  As long as we check for new channels at least
110                          * once every 10ms, we should be fine. */
111                         usleep(10000);
112                         continue;
113                 }
114
115                 chan = ast_waitfor_n(mons, x, &ms);
116                 if (!chan) {
117                         continue;
118                 }
119
120                 callid = ast_channel_callid(chan);
121                 ast_callid_threadassoc_change(callid);
122
123                 f = ast_read(chan);
124                 if (f) {
125                         ast_frfree(f);
126                 }
127         }
128
129         ast_callid_threadassoc_change(0);
130         asthread = AST_PTHREADT_NULL;
131
132         return NULL;
133 }
134
135 int ast_autoservice_start(struct ast_channel *chan)
136 {
137         int res = 0;
138         struct asent *as;
139
140         AST_LIST_LOCK(&aslist);
141         AST_LIST_TRAVERSE(&aslist, as, list) {
142                 if (as->chan == chan) {
143                         as->use_count++;
144                         break;
145                 }
146         }
147         AST_LIST_UNLOCK(&aslist);
148
149         if (as) {
150                 /* Entry exists, autoservice is already handling this channel */
151                 return 0;
152         }
153
154         if (!(as = ast_calloc(1, sizeof(*as))))
155                 return -1;
156
157         /* New entry created */
158         as->chan = chan;
159         as->use_count = 1;
160
161         ast_channel_lock(chan);
162         as->orig_end_dtmf_flag = ast_test_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY) ? 1 : 0;
163         if (!as->orig_end_dtmf_flag)
164                 ast_set_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY);
165         ast_channel_start_defer_frames(chan);
166         ast_channel_unlock(chan);
167
168         AST_LIST_LOCK(&aslist);
169
170         if (AST_LIST_EMPTY(&aslist) && asthread != AST_PTHREADT_NULL) {
171                 ast_cond_signal(&as_cond);
172         }
173
174         AST_LIST_INSERT_HEAD(&aslist, as, list);
175
176         if (asthread == AST_PTHREADT_NULL) { /* need start the thread */
177                 if (ast_pthread_create_background(&asthread, NULL, autoservice_run, NULL)) {
178                         ast_log(LOG_WARNING, "Unable to create autoservice thread :(\n");
179                         /* There will only be a single member in the list at this point,
180                            the one we just added. */
181                         AST_LIST_REMOVE(&aslist, as, list);
182                         ast_free(as);
183                         asthread = AST_PTHREADT_NULL;
184                         res = -1;
185                 } else {
186                         pthread_kill(asthread, SIGURG);
187                 }
188         }
189
190         AST_LIST_UNLOCK(&aslist);
191
192         return res;
193 }
194
195 int ast_autoservice_stop(struct ast_channel *chan)
196 {
197         int res = -1;
198         struct asent *as, *removed = NULL;
199         int chan_list_state;
200
201         AST_LIST_LOCK(&aslist);
202
203         /* Save the autoservice channel list state.  We _must_ verify that the channel
204          * list has been rebuilt before we return.  Because, after we return, the channel
205          * could get destroyed and we don't want our poor autoservice thread to step on
206          * it after its gone! */
207         chan_list_state = as_chan_list_state;
208
209         /* Find the entry, but do not free it because it still can be in the
210            autoservice thread array */
211         AST_LIST_TRAVERSE_SAFE_BEGIN(&aslist, as, list) {
212                 if (as->chan == chan) {
213                         as->use_count--;
214                         if (as->use_count < 1) {
215                                 AST_LIST_REMOVE_CURRENT(list);
216                                 removed = as;
217                         }
218                         break;
219                 }
220         }
221         AST_LIST_TRAVERSE_SAFE_END;
222
223         if (removed && asthread != AST_PTHREADT_NULL) {
224                 pthread_kill(asthread, SIGURG);
225         }
226
227         AST_LIST_UNLOCK(&aslist);
228
229         if (!removed) {
230                 return 0;
231         }
232
233         /* Wait while autoservice thread rebuilds its list. */
234         while (chan_list_state == as_chan_list_state) {
235                 usleep(1000);
236         }
237
238         /* Now autoservice thread should have no references to our entry
239            and we can safely destroy it */
240
241         if (!ast_channel_softhangup_internal_flag(chan)) {
242                 res = 0;
243         }
244
245         if (!as->orig_end_dtmf_flag) {
246                 ast_clear_flag(ast_channel_flags(chan), AST_FLAG_END_DTMF_ONLY);
247         }
248
249         ast_channel_lock(chan);
250         ast_channel_stop_defer_frames(chan);
251         ast_channel_unlock(chan);
252
253         ast_free(as);
254
255         return res;
256 }
257
258 void ast_autoservice_chan_hangup_peer(struct ast_channel *chan, struct ast_channel *peer)
259 {
260         if (chan && !ast_autoservice_start(chan)) {
261                 ast_hangup(peer);
262                 ast_autoservice_stop(chan);
263         } else {
264                 ast_hangup(peer);
265         }
266 }
267
268 int ast_autoservice_ignore(struct ast_channel *chan, enum ast_frame_type ftype)
269 {
270         struct asent *as;
271         int res = -1;
272
273         AST_LIST_LOCK(&aslist);
274         AST_LIST_TRAVERSE(&aslist, as, list) {
275                 if (as->chan == chan) {
276                         res = 0;
277                         as->ignore_frame_types |= (1 << ftype);
278                         break;
279                 }
280         }
281         AST_LIST_UNLOCK(&aslist);
282         return res;
283 }
284
285 static void autoservice_shutdown(void)
286 {
287         pthread_t th = asthread;
288         asexit = 1;
289         if (th != AST_PTHREADT_NULL) {
290                 ast_cond_signal(&as_cond);
291                 pthread_kill(th, SIGURG);
292                 pthread_join(th, NULL);
293         }
294 }
295
296 void ast_autoservice_init(void)
297 {
298         ast_register_cleanup(autoservice_shutdown);
299         ast_cond_init(&as_cond, NULL);
300 }