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