ARI: Add ability to raise arbitrary User Events
[asterisk/asterisk.git] / main / sched.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2010, 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 Scheduler Routines (from cheops-NG)
23  *
24  * \author Mark Spencer <markster@digium.com>
25  */
26
27 /*** MODULEINFO
28         <support_level>core</support_level>
29  ***/
30
31 #include "asterisk.h"
32
33 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
34
35 #ifdef DEBUG_SCHEDULER
36 #define DEBUG(a) do { \
37         if (option_debug) \
38                 DEBUG_M(a) \
39         } while (0)
40 #else
41 #define DEBUG(a)
42 #endif
43
44 #include <sys/time.h>
45
46 #include "asterisk/sched.h"
47 #include "asterisk/channel.h"
48 #include "asterisk/lock.h"
49 #include "asterisk/utils.h"
50 #include "asterisk/heap.h"
51 #include "asterisk/threadstorage.h"
52
53 /*!
54  * \brief Max num of schedule structs
55  *
56  * \note The max number of schedule structs to keep around
57  * for use.  Undefine to disable schedule structure
58  * caching. (Only disable this on very low memory
59  * machines)
60  */
61 #define SCHED_MAX_CACHE 128
62
63 AST_THREADSTORAGE(last_del_id);
64
65 struct sched {
66         AST_LIST_ENTRY(sched) list;
67         int id;                       /*!< ID number of event */
68         struct timeval when;          /*!< Absolute time event should take place */
69         int resched;                  /*!< When to reschedule */
70         int variable;                 /*!< Use return value from callback to reschedule */
71         const void *data;             /*!< Data */
72         ast_sched_cb callback;        /*!< Callback */
73         ssize_t __heap_index;
74 };
75
76 struct sched_thread {
77         pthread_t thread;
78         ast_cond_t cond;
79         unsigned int stop:1;
80 };
81
82 struct ast_sched_context {
83         ast_mutex_t lock;
84         unsigned int eventcnt;                  /*!< Number of events processed */
85         unsigned int highwater;                                 /*!< highest count so far */
86         struct ast_heap *sched_heap;
87         struct sched_thread *sched_thread;
88
89 #ifdef SCHED_MAX_CACHE
90         AST_LIST_HEAD_NOLOCK(, sched) schedc;   /*!< Cache of unused schedule structures and how many */
91         unsigned int schedccnt;
92 #endif
93 };
94
95 static void *sched_run(void *data)
96 {
97         struct ast_sched_context *con = data;
98
99         while (!con->sched_thread->stop) {
100                 int ms;
101                 struct timespec ts = {
102                         .tv_sec = 0,
103                 };
104
105                 ast_mutex_lock(&con->lock);
106
107                 if (con->sched_thread->stop) {
108                         ast_mutex_unlock(&con->lock);
109                         return NULL;
110                 }
111
112                 ms = ast_sched_wait(con);
113
114                 if (ms == -1) {
115                         ast_cond_wait(&con->sched_thread->cond, &con->lock);
116                 } else {
117                         struct timeval tv;
118                         tv = ast_tvadd(ast_tvnow(), ast_samp2tv(ms, 1000));
119                         ts.tv_sec = tv.tv_sec;
120                         ts.tv_nsec = tv.tv_usec * 1000;
121                         ast_cond_timedwait(&con->sched_thread->cond, &con->lock, &ts);
122                 }
123
124                 ast_mutex_unlock(&con->lock);
125
126                 if (con->sched_thread->stop) {
127                         return NULL;
128                 }
129
130                 ast_sched_runq(con);
131         }
132
133         return NULL;
134 }
135
136 static void sched_thread_destroy(struct ast_sched_context *con)
137 {
138         if (!con->sched_thread) {
139                 return;
140         }
141
142         if (con->sched_thread->thread != AST_PTHREADT_NULL) {
143                 ast_mutex_lock(&con->lock);
144                 con->sched_thread->stop = 1;
145                 ast_cond_signal(&con->sched_thread->cond);
146                 ast_mutex_unlock(&con->lock);
147                 pthread_join(con->sched_thread->thread, NULL);
148                 con->sched_thread->thread = AST_PTHREADT_NULL;
149         }
150
151         ast_cond_destroy(&con->sched_thread->cond);
152
153         ast_free(con->sched_thread);
154
155         con->sched_thread = NULL;
156 }
157
158 int ast_sched_start_thread(struct ast_sched_context *con)
159 {
160         struct sched_thread *st;
161
162         if (con->sched_thread) {
163                 ast_log(LOG_ERROR, "Thread already started on this scheduler context\n");
164                 return -1;
165         }
166
167         if (!(st = ast_calloc(1, sizeof(*st)))) {
168                 return -1;
169         }
170
171         ast_cond_init(&st->cond, NULL);
172
173         st->thread = AST_PTHREADT_NULL;
174
175         con->sched_thread = st;
176
177         if (ast_pthread_create_background(&st->thread, NULL, sched_run, con)) {
178                 ast_log(LOG_ERROR, "Failed to create scheduler thread\n");
179                 sched_thread_destroy(con);
180                 return -1;
181         }
182
183         return 0;
184 }
185
186 static int sched_time_cmp(void *a, void *b)
187 {
188         return ast_tvcmp(((struct sched *) b)->when, ((struct sched *) a)->when);
189 }
190
191 struct ast_sched_context *ast_sched_context_create(void)
192 {
193         struct ast_sched_context *tmp;
194
195         if (!(tmp = ast_calloc(1, sizeof(*tmp)))) {
196                 return NULL;
197         }
198
199         ast_mutex_init(&tmp->lock);
200         tmp->eventcnt = 1;
201
202         if (!(tmp->sched_heap = ast_heap_create(8, sched_time_cmp,
203                         offsetof(struct sched, __heap_index)))) {
204                 ast_sched_context_destroy(tmp);
205                 return NULL;
206         }
207
208         return tmp;
209 }
210
211 void ast_sched_context_destroy(struct ast_sched_context *con)
212 {
213         struct sched *s;
214
215         sched_thread_destroy(con);
216         con->sched_thread = NULL;
217
218         ast_mutex_lock(&con->lock);
219
220 #ifdef SCHED_MAX_CACHE
221         while ((s = AST_LIST_REMOVE_HEAD(&con->schedc, list))) {
222                 ast_free(s);
223         }
224 #endif
225
226         if (con->sched_heap) {
227                 while ((s = ast_heap_pop(con->sched_heap))) {
228                         ast_free(s);
229                 }
230                 ast_heap_destroy(con->sched_heap);
231                 con->sched_heap = NULL;
232         }
233
234         ast_mutex_unlock(&con->lock);
235         ast_mutex_destroy(&con->lock);
236
237         ast_free(con);
238 }
239
240 static struct sched *sched_alloc(struct ast_sched_context *con)
241 {
242         struct sched *tmp;
243
244         /*
245          * We keep a small cache of schedule entries
246          * to minimize the number of necessary malloc()'s
247          */
248 #ifdef SCHED_MAX_CACHE
249         if ((tmp = AST_LIST_REMOVE_HEAD(&con->schedc, list)))
250                 con->schedccnt--;
251         else
252 #endif
253                 tmp = ast_calloc(1, sizeof(*tmp));
254
255         return tmp;
256 }
257
258 static void sched_release(struct ast_sched_context *con, struct sched *tmp)
259 {
260         /*
261          * Add to the cache, or just free() if we
262          * already have too many cache entries
263          */
264
265 #ifdef SCHED_MAX_CACHE
266         if (con->schedccnt < SCHED_MAX_CACHE) {
267                 AST_LIST_INSERT_HEAD(&con->schedc, tmp, list);
268                 con->schedccnt++;
269         } else
270 #endif
271                 ast_free(tmp);
272 }
273
274 /*! \brief
275  * Return the number of milliseconds
276  * until the next scheduled event
277  */
278 int ast_sched_wait(struct ast_sched_context *con)
279 {
280         int ms;
281         struct sched *s;
282
283         DEBUG(ast_debug(1, "ast_sched_wait()\n"));
284
285         ast_mutex_lock(&con->lock);
286         if ((s = ast_heap_peek(con->sched_heap, 1))) {
287                 ms = ast_tvdiff_ms(s->when, ast_tvnow());
288                 if (ms < 0) {
289                         ms = 0;
290                 }
291         } else {
292                 ms = -1;
293         }
294         ast_mutex_unlock(&con->lock);
295
296         return ms;
297 }
298
299
300 /*! \brief
301  * Take a sched structure and put it in the
302  * queue, such that the soonest event is
303  * first in the list.
304  */
305 static void schedule(struct ast_sched_context *con, struct sched *s)
306 {
307         ast_heap_push(con->sched_heap, s);
308
309         if (ast_heap_size(con->sched_heap) > con->highwater) {
310                 con->highwater = ast_heap_size(con->sched_heap);
311         }
312 }
313
314 /*! \brief
315  * given the last event *tv and the offset in milliseconds 'when',
316  * computes the next value,
317  */
318 static int sched_settime(struct timeval *t, int when)
319 {
320         struct timeval now = ast_tvnow();
321
322         /*ast_debug(1, "TV -> %lu,%lu\n", tv->tv_sec, tv->tv_usec);*/
323         if (ast_tvzero(*t))     /* not supplied, default to now */
324                 *t = now;
325         *t = ast_tvadd(*t, ast_samp2tv(when, 1000));
326         if (ast_tvcmp(*t, now) < 0) {
327                 *t = now;
328         }
329         return 0;
330 }
331
332 int ast_sched_replace_variable(int old_id, struct ast_sched_context *con, int when, ast_sched_cb callback, const void *data, int variable)
333 {
334         /* 0 means the schedule item is new; do not delete */
335         if (old_id > 0) {
336                 AST_SCHED_DEL(con, old_id);
337         }
338         return ast_sched_add_variable(con, when, callback, data, variable);
339 }
340
341 /*! \brief
342  * Schedule callback(data) to happen when ms into the future
343  */
344 int ast_sched_add_variable(struct ast_sched_context *con, int when, ast_sched_cb callback, const void *data, int variable)
345 {
346         struct sched *tmp;
347         int res = -1;
348
349         DEBUG(ast_debug(1, "ast_sched_add()\n"));
350
351         ast_mutex_lock(&con->lock);
352         if ((tmp = sched_alloc(con))) {
353                 tmp->id = con->eventcnt++;
354                 tmp->callback = callback;
355                 tmp->data = data;
356                 tmp->resched = when;
357                 tmp->variable = variable;
358                 tmp->when = ast_tv(0, 0);
359                 if (sched_settime(&tmp->when, when)) {
360                         sched_release(con, tmp);
361                 } else {
362                         schedule(con, tmp);
363                         res = tmp->id;
364                 }
365         }
366 #ifdef DUMP_SCHEDULER
367         /* Dump contents of the context while we have the lock so nothing gets screwed up by accident. */
368         if (option_debug)
369                 ast_sched_dump(con);
370 #endif
371         if (con->sched_thread) {
372                 ast_cond_signal(&con->sched_thread->cond);
373         }
374         ast_mutex_unlock(&con->lock);
375
376         return res;
377 }
378
379 int ast_sched_replace(int old_id, struct ast_sched_context *con, int when, ast_sched_cb callback, const void *data)
380 {
381         if (old_id > -1) {
382                 AST_SCHED_DEL(con, old_id);
383         }
384         return ast_sched_add(con, when, callback, data);
385 }
386
387 int ast_sched_add(struct ast_sched_context *con, int when, ast_sched_cb callback, const void *data)
388 {
389         return ast_sched_add_variable(con, when, callback, data, 0);
390 }
391
392 static struct sched *sched_find(struct ast_sched_context *con, int id)
393 {
394         int x;
395         size_t heap_size;
396
397         heap_size = ast_heap_size(con->sched_heap);
398         for (x = 1; x <= heap_size; x++) {
399                 struct sched *cur = ast_heap_peek(con->sched_heap, x);
400
401                 if (cur->id == id) {
402                         return cur;
403                 }
404         }
405
406         return NULL;
407 }
408
409 const void *ast_sched_find_data(struct ast_sched_context *con, int id)
410 {
411         struct sched *s;
412         const void *data = NULL;
413
414         ast_mutex_lock(&con->lock);
415
416         s = sched_find(con, id);
417         if (s) {
418                 data = s->data;
419         }
420
421         ast_mutex_unlock(&con->lock);
422
423         return data;
424 }
425
426 /*! \brief
427  * Delete the schedule entry with number
428  * "id".  It's nearly impossible that there
429  * would be two or more in the list with that
430  * id.
431  */
432 #ifndef AST_DEVMODE
433 int ast_sched_del(struct ast_sched_context *con, int id)
434 #else
435 int _ast_sched_del(struct ast_sched_context *con, int id, const char *file, int line, const char *function)
436 #endif
437 {
438         struct sched *s = NULL;
439         int *last_id = ast_threadstorage_get(&last_del_id, sizeof(int));
440
441         DEBUG(ast_debug(1, "ast_sched_del(%d)\n", id));
442
443         if (id < 0) {
444                 return 0;
445         }
446
447         ast_mutex_lock(&con->lock);
448
449         s = sched_find(con, id);
450         if (s) {
451                 if (!ast_heap_remove(con->sched_heap, s)) {
452                         ast_log(LOG_WARNING,"sched entry %d not in the sched heap?\n", s->id);
453                 }
454
455                 sched_release(con, s);
456         }
457
458 #ifdef DUMP_SCHEDULER
459         /* Dump contents of the context while we have the lock so nothing gets screwed up by accident. */
460         if (option_debug)
461                 ast_sched_dump(con);
462 #endif
463         if (con->sched_thread) {
464                 ast_cond_signal(&con->sched_thread->cond);
465         }
466         ast_mutex_unlock(&con->lock);
467
468         if (!s && *last_id != id) {
469                 ast_debug(1, "Attempted to delete nonexistent schedule entry %d!\n", id);
470 #ifndef AST_DEVMODE
471                 ast_assert(s != NULL);
472 #else
473                 {
474                 char buf[100];
475                 snprintf(buf, sizeof(buf), "s != NULL, id=%d", id);
476                 _ast_assert(0, buf, file, line, function);
477                 }
478 #endif
479                 *last_id = id;
480                 return -1;
481         } else if (!s) {
482                 return -1;
483         }
484
485         return 0;
486 }
487
488 void ast_sched_report(struct ast_sched_context *con, struct ast_str **buf, struct ast_cb_names *cbnames)
489 {
490         int i, x;
491         struct sched *cur;
492         int countlist[cbnames->numassocs + 1];
493         size_t heap_size;
494
495         memset(countlist, 0, sizeof(countlist));
496         ast_str_set(buf, 0, " Highwater = %u\n schedcnt = %zu\n", con->highwater, ast_heap_size(con->sched_heap));
497
498         ast_mutex_lock(&con->lock);
499
500         heap_size = ast_heap_size(con->sched_heap);
501         for (x = 1; x <= heap_size; x++) {
502                 cur = ast_heap_peek(con->sched_heap, x);
503                 /* match the callback to the cblist */
504                 for (i = 0; i < cbnames->numassocs; i++) {
505                         if (cur->callback == cbnames->cblist[i]) {
506                                 break;
507                         }
508                 }
509                 if (i < cbnames->numassocs) {
510                         countlist[i]++;
511                 } else {
512                         countlist[cbnames->numassocs]++;
513                 }
514         }
515
516         ast_mutex_unlock(&con->lock);
517
518         for (i = 0; i < cbnames->numassocs; i++) {
519                 ast_str_append(buf, 0, "    %s : %d\n", cbnames->list[i], countlist[i]);
520         }
521
522         ast_str_append(buf, 0, "   <unknown> : %d\n", countlist[cbnames->numassocs]);
523 }
524
525 /*! \brief Dump the contents of the scheduler to LOG_DEBUG */
526 void ast_sched_dump(struct ast_sched_context *con)
527 {
528         struct sched *q;
529         struct timeval when = ast_tvnow();
530         int x;
531         size_t heap_size;
532 #ifdef SCHED_MAX_CACHE
533         ast_debug(1, "Asterisk Schedule Dump (%zu in Q, %u Total, %u Cache, %u high-water)\n", ast_heap_size(con->sched_heap), con->eventcnt - 1, con->schedccnt, con->highwater);
534 #else
535         ast_debug(1, "Asterisk Schedule Dump (%zu in Q, %u Total, %u high-water)\n", ast_heap_size(con->sched_heap), con->eventcnt - 1, con->highwater);
536 #endif
537
538         ast_debug(1, "=============================================================\n");
539         ast_debug(1, "|ID    Callback          Data              Time  (sec:ms)   |\n");
540         ast_debug(1, "+-----+-----------------+-----------------+-----------------+\n");
541         ast_mutex_lock(&con->lock);
542         heap_size = ast_heap_size(con->sched_heap);
543         for (x = 1; x <= heap_size; x++) {
544                 struct timeval delta;
545                 q = ast_heap_peek(con->sched_heap, x);
546                 delta = ast_tvsub(q->when, when);
547                 ast_debug(1, "|%.4d | %-15p | %-15p | %.6ld : %.6ld |\n",
548                         q->id,
549                         q->callback,
550                         q->data,
551                         (long)delta.tv_sec,
552                         (long int)delta.tv_usec);
553         }
554         ast_mutex_unlock(&con->lock);
555         ast_debug(1, "=============================================================\n");
556 }
557
558 /*! \brief
559  * Launch all events which need to be run at this time.
560  */
561 int ast_sched_runq(struct ast_sched_context *con)
562 {
563         struct sched *current;
564         struct timeval when;
565         int numevents;
566         int res;
567
568         DEBUG(ast_debug(1, "ast_sched_runq()\n"));
569
570         ast_mutex_lock(&con->lock);
571
572         when = ast_tvadd(ast_tvnow(), ast_tv(0, 1000));
573         for (numevents = 0; (current = ast_heap_peek(con->sched_heap, 1)); numevents++) {
574                 /* schedule all events which are going to expire within 1ms.
575                  * We only care about millisecond accuracy anyway, so this will
576                  * help us get more than one event at one time if they are very
577                  * close together.
578                  */
579                 if (ast_tvcmp(current->when, when) != -1) {
580                         break;
581                 }
582
583                 current = ast_heap_pop(con->sched_heap);
584
585                 /*
586                  * At this point, the schedule queue is still intact.  We
587                  * have removed the first event and the rest is still there,
588                  * so it's permissible for the callback to add new events, but
589                  * trying to delete itself won't work because it isn't in
590                  * the schedule queue.  If that's what it wants to do, it
591                  * should return 0.
592                  */
593
594                 ast_mutex_unlock(&con->lock);
595                 res = current->callback(current->data);
596                 ast_mutex_lock(&con->lock);
597
598                 if (res) {
599                         /*
600                          * If they return non-zero, we should schedule them to be
601                          * run again.
602                          */
603                         if (sched_settime(&current->when, current->variable? res : current->resched)) {
604                                 sched_release(con, current);
605                         } else {
606                                 schedule(con, current);
607                         }
608                 } else {
609                         /* No longer needed, so release it */
610                         sched_release(con, current);
611                 }
612         }
613
614         ast_mutex_unlock(&con->lock);
615
616         return numevents;
617 }
618
619 long ast_sched_when(struct ast_sched_context *con,int id)
620 {
621         struct sched *s;
622         long secs = -1;
623         DEBUG(ast_debug(1, "ast_sched_when()\n"));
624
625         ast_mutex_lock(&con->lock);
626
627         s = sched_find(con, id);
628         if (s) {
629                 struct timeval now = ast_tvnow();
630                 secs = s->when.tv_sec - now.tv_sec;
631         }
632
633         ast_mutex_unlock(&con->lock);
634
635         return secs;
636 }