A schedule id of 0 is not possible and is used to flag that we want to add a new...
[asterisk/asterisk.git] / main / sched.c
1 /*
2  * Asterisk -- An open source telephony toolkit.
3  *
4  * Copyright (C) 1999 - 2005, Digium, Inc.
5  *
6  * Mark Spencer <markster@digium.com>
7  *
8  * See http://www.asterisk.org for more information about
9  * the Asterisk project. Please do not directly contact
10  * any of the maintainers of this project for assistance;
11  * the project provides a web site, mailing lists and IRC
12  * channels for your use.
13  *
14  * This program is free software, distributed under the terms of
15  * the GNU General Public License Version 2. See the LICENSE file
16  * at the top of the source tree.
17  */
18
19 /*! \file
20  *
21  * \brief Scheduler Routines (from cheops-NG)
22  *
23  * \author Mark Spencer <markster@digium.com>
24  */
25
26 #include "asterisk.h"
27
28 ASTERISK_FILE_VERSION(__FILE__, "$Revision$")
29
30 #ifdef DEBUG_SCHEDULER
31 #define DEBUG(a) do { \
32         if (option_debug) \
33                 DEBUG_M(a) \
34         } while (0)
35 #else
36 #define DEBUG(a) 
37 #endif
38
39 #include <stdio.h>
40 #include <stdlib.h>
41 #include <sys/time.h>
42 #include <unistd.h>
43 #include <string.h>
44
45 #include "asterisk/sched.h"
46 #include "asterisk/logger.h"
47 #include "asterisk/channel.h"
48 #include "asterisk/lock.h"
49 #include "asterisk/utils.h"
50 #include "asterisk/linkedlists.h"
51 #include "asterisk/options.h"
52
53 struct sched {
54         AST_LIST_ENTRY(sched) list;
55         int id;                       /*!< ID number of event */
56         struct timeval when;          /*!< Absolute time event should take place */
57         int resched;                  /*!< When to reschedule */
58         int variable;                 /*!< Use return value from callback to reschedule */
59         void *data;                   /*!< Data */
60         ast_sched_cb callback;        /*!< Callback */
61 };
62
63 struct sched_context {
64         ast_mutex_t lock;
65         unsigned int eventcnt;                  /*!< Number of events processed */
66         unsigned int schedcnt;                  /*!< Number of outstanding schedule events */
67         AST_LIST_HEAD_NOLOCK(, sched) schedq;   /*!< Schedule entry and main queue */
68
69 #ifdef SCHED_MAX_CACHE
70         AST_LIST_HEAD_NOLOCK(, sched) schedc;   /*!< Cache of unused schedule structures and how many */
71         unsigned int schedccnt;
72 #endif
73 };
74
75 struct sched_context *sched_context_create(void)
76 {
77         struct sched_context *tmp;
78
79         if (!(tmp = ast_calloc(1, sizeof(*tmp))))
80                 return NULL;
81
82         ast_mutex_init(&tmp->lock);
83         tmp->eventcnt = 1;
84         
85         return tmp;
86 }
87
88 void sched_context_destroy(struct sched_context *con)
89 {
90         struct sched *s;
91
92         ast_mutex_lock(&con->lock);
93
94 #ifdef SCHED_MAX_CACHE
95         /* Eliminate the cache */
96         while ((s = AST_LIST_REMOVE_HEAD(&con->schedc, list)))
97                 ast_free(s);
98 #endif
99
100         /* And the queue */
101         while ((s = AST_LIST_REMOVE_HEAD(&con->schedq, list)))
102                 ast_free(s);
103         
104         /* And the context */
105         ast_mutex_unlock(&con->lock);
106         ast_mutex_destroy(&con->lock);
107         ast_free(con);
108 }
109
110 static struct sched *sched_alloc(struct sched_context *con)
111 {
112         struct sched *tmp;
113
114         /*
115          * We keep a small cache of schedule entries
116          * to minimize the number of necessary malloc()'s
117          */
118 #ifdef SCHED_MAX_CACHE
119         if ((tmp = AST_LIST_REMOVE_HEAD(&con->schedc, list)))
120                 con->schedccnt--;
121         else
122 #endif
123                 tmp = ast_calloc(1, sizeof(*tmp));
124
125         return tmp;
126 }
127
128 static void sched_release(struct sched_context *con, struct sched *tmp)
129 {
130         /*
131          * Add to the cache, or just free() if we
132          * already have too many cache entries
133          */
134
135 #ifdef SCHED_MAX_CACHE   
136         if (con->schedccnt < SCHED_MAX_CACHE) {
137                 AST_LIST_INSERT_HEAD(&con->schedc, tmp, list);
138                 con->schedccnt++;
139         } else
140 #endif
141                 ast_free(tmp);
142 }
143
144 /*! \brief
145  * Return the number of milliseconds 
146  * until the next scheduled event
147  */
148 int ast_sched_wait(struct sched_context *con)
149 {
150         int ms;
151
152         DEBUG(ast_debug(1, "ast_sched_wait()\n"));
153
154         ast_mutex_lock(&con->lock);
155         if (AST_LIST_EMPTY(&con->schedq)) {
156                 ms = -1;
157         } else {
158                 ms = ast_tvdiff_ms(AST_LIST_FIRST(&con->schedq)->when, ast_tvnow());
159                 if (ms < 0)
160                         ms = 0;
161         }
162         ast_mutex_unlock(&con->lock);
163
164         return ms;
165 }
166
167
168 /*! \brief
169  * Take a sched structure and put it in the
170  * queue, such that the soonest event is
171  * first in the list. 
172  */
173 static void schedule(struct sched_context *con, struct sched *s)
174 {
175          
176         struct sched *cur = NULL;
177         
178         AST_LIST_TRAVERSE_SAFE_BEGIN(&con->schedq, cur, list) {
179                 if (ast_tvcmp(s->when, cur->when) == -1) {
180                         AST_LIST_INSERT_BEFORE_CURRENT(&con->schedq, s, list);
181                         break;
182                 }
183         }
184         AST_LIST_TRAVERSE_SAFE_END
185         if (!cur)
186                 AST_LIST_INSERT_TAIL(&con->schedq, s, list);
187         
188         con->schedcnt++;
189 }
190
191 /*! \brief
192  * given the last event *tv and the offset in milliseconds 'when',
193  * computes the next value,
194  */
195 static int sched_settime(struct timeval *tv, int when)
196 {
197         struct timeval now = ast_tvnow();
198
199         /*ast_debug(1, "TV -> %lu,%lu\n", tv->tv_sec, tv->tv_usec);*/
200         if (ast_tvzero(*tv))    /* not supplied, default to now */
201                 *tv = now;
202         *tv = ast_tvadd(*tv, ast_samp2tv(when, 1000));
203         if (ast_tvcmp(*tv, now) < 0) {
204                 ast_debug(1, "Request to schedule in the past?!?!\n");
205                 *tv = now;
206         }
207         return 0;
208 }
209
210 int ast_sched_replace_variable(int old_id, struct sched_context *con, int when, ast_sched_cb callback, void *data, int variable)
211 {
212         /* 0 means the schedule item is new; do not delete */
213         if (old_id > 0)
214                 ast_sched_del(con, old_id);
215         return ast_sched_add_variable(con, when, callback, data, variable);
216 }
217
218 /*! \brief
219  * Schedule callback(data) to happen when ms into the future
220  */
221 int ast_sched_add_variable(struct sched_context *con, int when, ast_sched_cb callback, void *data, int variable)
222 {
223         struct sched *tmp;
224         int res = -1;
225         DEBUG(ast_debug(1, "ast_sched_add()\n"));
226         if (!when) {
227                 ast_log(LOG_NOTICE, "Scheduled event in 0 ms?\n");
228                 return -1;
229         }
230         ast_mutex_lock(&con->lock);
231         if ((tmp = sched_alloc(con))) {
232                 tmp->id = con->eventcnt++;
233                 tmp->callback = callback;
234                 tmp->data = data;
235                 tmp->resched = when;
236                 tmp->variable = variable;
237                 tmp->when = ast_tv(0, 0);
238                 if (sched_settime(&tmp->when, when)) {
239                         sched_release(con, tmp);
240                 } else {
241                         schedule(con, tmp);
242                         res = tmp->id;
243                 }
244         }
245 #ifdef DUMP_SCHEDULER
246         /* Dump contents of the context while we have the lock so nothing gets screwed up by accident. */
247         if (option_debug)
248                 ast_sched_dump(con);
249 #endif
250         ast_mutex_unlock(&con->lock);
251         return res;
252 }
253
254 int ast_sched_replace(int old_id, struct sched_context *con, int when, ast_sched_cb callback, void *data)
255 {
256         if (old_id > -1)
257                 ast_sched_del(con, old_id);
258         return ast_sched_add(con, when, callback, data);
259 }
260
261 int ast_sched_add(struct sched_context *con, int when, ast_sched_cb callback, void *data)
262 {
263         return ast_sched_add_variable(con, when, callback, data, 0);
264 }
265
266 /*! \brief
267  * Delete the schedule entry with number
268  * "id".  It's nearly impossible that there
269  * would be two or more in the list with that
270  * id.
271  */
272 int ast_sched_del(struct sched_context *con, int id)
273 {
274         struct sched *s;
275
276         DEBUG(ast_debug(1, "ast_sched_del()\n"));
277         
278         ast_mutex_lock(&con->lock);
279         AST_LIST_TRAVERSE_SAFE_BEGIN(&con->schedq, s, list) {
280                 if (s->id == id) {
281                         AST_LIST_REMOVE_CURRENT(&con->schedq, list);
282                         con->schedcnt--;
283                         sched_release(con, s);
284                         break;
285                 }
286         }
287         AST_LIST_TRAVERSE_SAFE_END
288
289 #ifdef DUMP_SCHEDULER
290         /* Dump contents of the context while we have the lock so nothing gets screwed up by accident. */
291         if (option_debug)
292                 ast_sched_dump(con);
293 #endif
294         ast_mutex_unlock(&con->lock);
295
296         if (!s) {
297                 ast_debug(1, "Attempted to delete nonexistent schedule entry %d!\n", id);
298 #ifdef DO_CRASH
299                 CRASH;
300 #endif
301                 return -1;
302         }
303         
304         return 0;
305 }
306
307 /*! \brief Dump the contents of the scheduler to LOG_DEBUG */
308 void ast_sched_dump(const struct sched_context *con)
309 {
310         struct sched *q;
311         struct timeval tv = ast_tvnow();
312 #ifdef SCHED_MAX_CACHE
313         ast_debug(1, "Asterisk Schedule Dump (%d in Q, %d Total, %d Cache)\n", con->schedcnt, con->eventcnt - 1, con->schedccnt);
314 #else
315         ast_debug(1, "Asterisk Schedule Dump (%d in Q, %d Total)\n", con->schedcnt, con->eventcnt - 1);
316 #endif
317
318         ast_debug(1, "=============================================================\n");
319         ast_debug(1, "|ID    Callback          Data              Time  (sec:ms)   |\n");
320         ast_debug(1, "+-----+-----------------+-----------------+-----------------+\n");
321         AST_LIST_TRAVERSE(&con->schedq, q, list) {
322                 struct timeval delta = ast_tvsub(q->when, tv);
323
324                 ast_debug(1, "|%.4d | %-15p | %-15p | %.6ld : %.6ld |\n", 
325                         q->id,
326                         q->callback,
327                         q->data,
328                         delta.tv_sec,
329                         (long int)delta.tv_usec);
330         }
331         ast_debug(1, "=============================================================\n");
332 }
333
334 /*! \brief
335  * Launch all events which need to be run at this time.
336  */
337 int ast_sched_runq(struct sched_context *con)
338 {
339         struct sched *current;
340         struct timeval tv;
341         int numevents;
342         int res;
343
344         DEBUG(ast_debug(1, "ast_sched_runq()\n"));
345                 
346         ast_mutex_lock(&con->lock);
347
348         for (numevents = 0; !AST_LIST_EMPTY(&con->schedq); numevents++) {
349                 /* schedule all events which are going to expire within 1ms.
350                  * We only care about millisecond accuracy anyway, so this will
351                  * help us get more than one event at one time if they are very
352                  * close together.
353                  */
354                 tv = ast_tvadd(ast_tvnow(), ast_tv(0, 1000));
355                 if (ast_tvcmp(AST_LIST_FIRST(&con->schedq)->when, tv) != -1)
356                         break;
357                 
358                 current = AST_LIST_REMOVE_HEAD(&con->schedq, list);
359                 con->schedcnt--;
360
361                 /*
362                  * At this point, the schedule queue is still intact.  We
363                  * have removed the first event and the rest is still there,
364                  * so it's permissible for the callback to add new events, but
365                  * trying to delete itself won't work because it isn't in
366                  * the schedule queue.  If that's what it wants to do, it 
367                  * should return 0.
368                  */
369                         
370                 ast_mutex_unlock(&con->lock);
371                 res = current->callback(current->data);
372                 ast_mutex_lock(&con->lock);
373                         
374                 if (res) {
375                         /*
376                          * If they return non-zero, we should schedule them to be
377                          * run again.
378                          */
379                         if (sched_settime(&current->when, current->variable? res : current->resched)) {
380                                 sched_release(con, current);
381                         } else
382                                 schedule(con, current);
383                 } else {
384                         /* No longer needed, so release it */
385                         sched_release(con, current);
386                 }
387         }
388
389         ast_mutex_unlock(&con->lock);
390         
391         return numevents;
392 }
393
394 long ast_sched_when(struct sched_context *con,int id)
395 {
396         struct sched *s;
397         long secs = -1;
398         DEBUG(ast_debug(1, "ast_sched_when()\n"));
399
400         ast_mutex_lock(&con->lock);
401         AST_LIST_TRAVERSE(&con->schedq, s, list) {
402                 if (s->id == id)
403                         break;
404         }
405         if (s) {
406                 struct timeval now = ast_tvnow();
407                 secs = s->when.tv_sec - now.tv_sec;
408         }
409         ast_mutex_unlock(&con->lock);
410         
411         return secs;
412 }