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