Initial revision
[silc.git] / lib / silccore / silcschedule.c
1 /*
2
3   silcschedule.c
4
5   Author: Pekka Riikonen <priikone@poseidon.pspt.fi>
6
7   Copyright (C) 1998 - 2000 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; either version 2 of the License, or
12   (at your option) any later version.
13   
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18
19 */
20 /*
21  * $Id$
22  * $Log$
23  * Revision 1.1  2000/06/27 11:36:55  priikone
24  * Initial revision
25  *
26  *
27  */
28
29 #include "silcincludes.h"
30
31 /* The actual schedule object. */
32 static SilcSchedule schedule;
33
34 /* Initializes the schedule. Sets the non-timeout task queue hook and
35    the timeout task queue hook. This must be called before the schedule
36    is able to work. */
37
38 void silc_schedule_init(SilcTaskQueue fd_queue,
39                         SilcTaskQueue timeout_queue,
40                         SilcTaskQueue generic_queue,
41                         int max_fd)
42 {
43   int i;
44
45   SILC_LOG_DEBUG(("Initializing scheduler"));
46
47   /* Initialize the schedule */
48   memset(&schedule, 0, sizeof(schedule));
49   schedule.fd_queue = fd_queue;
50   schedule.timeout_queue = timeout_queue;
51   schedule.generic_queue = generic_queue;
52   schedule.fd_list.fd = silc_calloc(max_fd, sizeof(int));
53   schedule.fd_list.last_fd = 0;
54   schedule.fd_list.max_fd = max_fd;
55   schedule.timeout = NULL;
56   schedule.valid = TRUE;
57   FD_ZERO(&schedule.in);
58   FD_ZERO(&schedule.out);
59   schedule.max_fd = -1;
60   for (i = 0; i < max_fd; i++)
61     schedule.fd_list.fd[i] = -1;
62 }
63
64 /* Uninitializes the schedule. This is called when the program is ready
65    to end. This removes all tasks and task queues. */
66
67 int silc_schedule_uninit()
68 {
69
70   SILC_LOG_DEBUG(("Uninitializing scheduler"));
71
72   if (schedule.valid == TRUE)
73     return FALSE;
74
75   /* Unregister all tasks */
76   if (schedule.fd_queue)
77     silc_task_remove(schedule.fd_queue, SILC_ALL_TASKS);
78   if (schedule.timeout_queue)
79     silc_task_remove(schedule.timeout_queue, SILC_ALL_TASKS);
80   if (schedule.generic_queue)
81     silc_task_remove(schedule.generic_queue, SILC_ALL_TASKS);
82
83   /* Unregister all task queues */
84   if (schedule.fd_queue)
85     silc_task_queue_free(schedule.fd_queue);
86   if (schedule.timeout_queue)
87     silc_task_queue_free(schedule.timeout_queue);
88   if (schedule.generic_queue)
89     silc_task_queue_free(schedule.generic_queue);
90
91   /* Clear the fd list */
92   if (schedule.fd_list.fd) {
93     memset(schedule.fd_list.fd, -1, schedule.fd_list.max_fd);
94     silc_free(schedule.fd_list.fd);
95   }
96
97   memset(&schedule, 'F', sizeof(schedule));
98   return TRUE;
99 }
100
101 /* Stops the schedule even if it is not supposed to be stopped yet. 
102    After calling this, one should call silc_schedule_uninit (after the 
103    silc_schedule has returned). */
104
105 void silc_schedule_stop()
106 {
107   SILC_LOG_DEBUG(("Stopping scheduler"));
108
109   if (schedule.valid == TRUE)
110     schedule.valid = FALSE;
111 }
112
113 /* Sets a file descriptor to be listened by select() in scheduler. One can
114    call this directly if wanted. This can be called multiple times for
115    one file descriptor to set different iomasks. */
116
117 void silc_schedule_set_listen_fd(int fd, unsigned int iomask)
118 {
119   assert(schedule.valid != FALSE);
120   assert(fd < schedule.fd_list.max_fd);
121
122   schedule.fd_list.fd[fd] = iomask;
123   
124   if (fd > schedule.fd_list.last_fd)
125     schedule.fd_list.last_fd = fd;
126 }
127
128 /* Removes a file descriptor from listen list. */
129
130 void silc_schedule_unset_listen_fd(int fd)
131 {
132   assert(schedule.valid != FALSE);
133   assert(fd < schedule.fd_list.max_fd);
134
135   schedule.fd_list.fd[fd] = -1;
136   
137   if (fd == schedule.fd_list.last_fd) {
138     int i;
139
140     for (i = fd; i >= 0; i--)
141       if (schedule.fd_list.fd[i] != -1)
142         break;
143
144     schedule.fd_list.last_fd = i;
145   }
146 }
147
148 /* Executes tasks matching the file descriptor set by select(). The task
149    remains on the task queue after execution. Invalid tasks are removed 
150    here from the task queue. This macro is used by silc_schedule function. 
151    We don't have to care about the tasks priority here because the tasks
152    are sorted in their priority order already at the registration phase. */
153
154 #define SILC_SCHEDULE_RUN_TASKS                                            \
155 do {                                                                       \
156   queue = schedule.fd_queue;                                               \
157   if (queue && queue->valid == TRUE && queue->task) {                      \
158     task = queue->task;                                                    \
159                                                                            \
160     /* Walk thorugh all tasks in the particular task queue and             \
161        execute the callback functions of those tasks matching the          \
162        fd set by select(). */                                              \
163     while(1) {                                                             \
164       /* Validity of the task is checked always before and after           \
165          execution beacuse the task might have been unregistered           \
166          in the callback function, ie. it is not valid anymore. */         \
167                                                                            \
168       if (task->valid) {                                                   \
169         /* Task ready for reading */                                       \
170         if ((FD_ISSET(task->fd, &schedule.in)) &&                          \
171             (task->iomask & (1L << SILC_TASK_READ))) {                     \
172           task->callback(queue, SILC_TASK_READ, task->context, task->fd);  \
173           is_run = TRUE;                                                   \
174         }                                                                  \
175       }                                                                    \
176                                                                            \
177       if (task->valid) {                                                   \
178         /* Task ready for writing */                                       \
179         if ((FD_ISSET(task->fd, &schedule.out)) &&                         \
180             (task->iomask & (1L << SILC_TASK_WRITE))) {                    \
181           task->callback(queue, SILC_TASK_WRITE, task->context, task->fd); \
182           is_run = TRUE;                                                   \
183         }                                                                  \
184       }                                                                    \
185                                                                            \
186       if (!task->valid) {                                                  \
187         /* Invalid (unregistered) tasks are removed from the               \
188            task queue. */                                                  \
189         if (queue->task == task->next) {                                   \
190           silc_task_remove(queue, task);                                   \
191           break;                                                           \
192         }                                                                  \
193                                                                            \
194         task = task->next;                                                 \
195         silc_task_remove(queue, task->prev);                               \
196         continue;                                                          \
197       }                                                                    \
198                                                                            \
199       /* Break if there isn't more tasks in the queue */                   \
200       if (queue->task == task->next)                                       \
201         break;                                                             \
202                                                                            \
203       task = task->next;                                                   \
204     }                                                                      \
205   }                                                                        \
206 } while(0)
207
208 /* Selects tasks to be listened by select(). These are the non-timeout
209    tasks. This checks the scheduler's fd list. This macro is used by 
210    silc_schedule function. */
211
212 #define SILC_SCHEDULE_SELECT_TASKS                              \
213 do {                                                            \
214   for (i = 0; i <= schedule.fd_list.last_fd; i++) {             \
215     if (schedule.fd_list.fd[i] != -1) {                         \
216                                                                 \
217       /* Set the max fd value for select() to listen */         \
218       if (i > schedule.max_fd)                                  \
219         schedule.max_fd = i;                                    \
220                                                                 \
221       /* Add tasks for reading */                               \
222       if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_READ)))    \
223         FD_SET(i, &schedule.in);                                \
224                                                                 \
225       /* Add tasks for writing */                               \
226       if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_WRITE)))   \
227         FD_SET(i, &schedule.out);                               \
228     }                                                           \
229   }                                                             \
230 } while(0)
231
232 /* Executes all tasks whose timeout has expired. The task is removed from
233    the task queue after the callback function has returned. Also, invalid
234    tasks are removed here. The current time must be get before calling this
235    macro. This macro is used by silc_schedule function. We don't have to
236    care about priorities because tasks are already sorted in their priority
237    order at the registration phase. */
238
239 #define SILC_SCHEDULE_RUN_TIMEOUT_TASKS                                 \
240 do {                                                                    \
241   queue = schedule.timeout_queue;                                       \
242   if (queue && queue->valid == TRUE && queue->task) {                   \
243     task = queue->task;                                                 \
244                                                                         \
245     /* Walk thorugh all tasks in the particular task queue              \
246        and run all the expired tasks. */                                \
247     while(1) {                                                          \
248       /* Execute the task if the timeout has expired */                 \
249       if (silc_task_timeout_compare(&task->timeout, &curtime)) {        \
250                                                                         \
251         /* Task ready for reading */                                    \
252         if (task->valid) {                                              \
253           if ((task->iomask & (1L << SILC_TASK_READ)))                  \
254             task->callback(queue, SILC_TASK_READ,                       \
255                            task->context, task->fd);                    \
256         }                                                               \
257                                                                         \
258         /* Task ready for writing */                                    \
259         if (task->valid) {                                              \
260           if ((task->iomask & (1L << SILC_TASK_WRITE)))                 \
261             task->callback(queue, SILC_TASK_WRITE,                      \
262                            task->context, task->fd);                    \
263         }                                                               \
264                                                                         \
265         /* Break if there isn't more tasks in the queue */              \
266         if (queue->task == task->next) {                                \
267           /* Remove the task from queue */                              \
268           silc_task_remove(queue, task);                                \
269           break;                                                        \
270         }                                                               \
271                                                                         \
272         task = task->next;                                              \
273                                                                         \
274         /* Remove the task from queue */                                \
275         silc_task_remove(queue, task->prev);                            \
276       } else {                                                          \
277         /* The timeout hasn't expired, check for next one */            \
278                                                                         \
279         /* Break if there isn't more tasks in the queue */              \
280         if (queue->task == task->next)                                  \
281           break;                                                        \
282                                                                         \
283         task = task->next;                                              \
284       }                                                                 \
285     }                                                                   \
286   }                                                                     \
287 } while(0)
288
289 /* Calculates next timeout for select(). This is the timeout value
290    when at earliest some of the timeout tasks expire. If this is in the
291    past, they will be run now. This macro is used by the silc_schedule
292    function. */
293
294 #define SILC_SCHEDULE_SELECT_TIMEOUT                                        \
295 do {                                                                        \
296   if (schedule.timeout_queue && schedule.timeout_queue->valid == TRUE) {    \
297     queue = schedule.timeout_queue;                                         \
298     task = NULL;                                                            \
299                                                                             \
300     /* Get the current time */                                              \
301     gettimeofday(&curtime, NULL);                                           \
302     schedule.timeout = NULL;                                                \
303                                                                             \
304     /* First task in the task queue has always the smallest timeout. */     \
305     task = queue->task;                                                     \
306     while(1) {                                                              \
307       if (task && task->valid == TRUE) {                                    \
308                                                                             \
309         /* If the timeout is in past, we will run the task and all other    \
310            timeout tasks from the past. */                                  \
311         if (silc_task_timeout_compare(&task->timeout, &curtime)) {          \
312           SILC_SCHEDULE_RUN_TIMEOUT_TASKS;                                  \
313                                                                             \
314           /* The task(s) has expired and doesn't exist on the task queue    \
315              anymore. We continue with new timeout. */                      \
316           queue = schedule.timeout_queue;                                   \
317           task = queue->task;                                               \
318           if (task == NULL || task->valid == FALSE)                         \
319             break;                                                          \
320           goto cont;                                                        \
321         } else {                                                            \
322  cont:                                                                      \
323           /* Calculate the next timeout for select() */                     \
324           queue->timeout.tv_sec = task->timeout.tv_sec - curtime.tv_sec;    \
325           queue->timeout.tv_usec = task->timeout.tv_usec - curtime.tv_usec; \
326                                                                             \
327           /* We wouldn't want to go under zero, check for it. */            \
328           if (queue->timeout.tv_usec < 0) {                                 \
329             queue->timeout.tv_sec -= 1;                                     \
330             queue->timeout.tv_usec += 1000000L;                             \
331           }                                                                 \
332         }                                                                   \
333         /* We've got the timeout value */                                   \
334         break;                                                              \
335       } else {                                                              \
336         /* Task is not valid, remove it and try next one. */                \
337         silc_task_remove(queue, task);                                      \
338         task = queue->task;                                                 \
339         if (queue->task == NULL)                                            \
340           break;                                                            \
341       }                                                                     \
342     }                                                                       \
343     /* Save the timeout */                                                  \
344     if (task)                                                               \
345       schedule.timeout = &queue->timeout;                                   \
346   }                                                                         \
347 } while(0)
348
349 /* Execute generic tasks. These are executed only and only if for the
350    specific fd there wasn't other non-timeout tasks. This checks the earlier
351    set fd list, thus the generic tasks apply to all specified fd's. All the
352    generic tasks are executed at once. */
353
354 #define SILC_SCHEDULE_RUN_GENERIC_TASKS                                      \
355 do {                                                                         \
356   if (is_run == FALSE) {                                                     \
357     SILC_LOG_DEBUG(("Running generic tasks"));                               \
358     for (i = 0; i <= schedule.fd_list.last_fd; i++)                          \
359       if (schedule.fd_list.fd[i] != -1) {                                    \
360                                                                              \
361         /* Check whether this fd is select()ed. */                           \
362         if ((FD_ISSET(i, &schedule.in)) || (FD_ISSET(i, &schedule.out))) {   \
363                                                                              \
364           /* It was selected. Now find the tasks from task queue and execute \
365              all generic tasks. */                                           \
366           if (schedule.generic_queue && schedule.generic_queue->valid) {     \
367             queue = schedule.generic_queue;                                  \
368                                                                              \
369             if (!queue->task)                                                \
370               break;                                                         \
371                                                                              \
372             task = queue->task;                                              \
373                                                                              \
374             while(1) {                                                       \
375               /* Validity of the task is checked always before and after     \
376                  execution beacuse the task might have been unregistered     \
377                  in the callback function, ie. it is not valid anymore. */   \
378                                                                              \
379               if (task->valid && schedule.fd_list.fd[i] != -1) {             \
380                 /* Task ready for reading */                                 \
381                 if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_READ)))       \
382                   task->callback(queue, SILC_TASK_READ,                      \
383                                  task->context, i);                          \
384               }                                                              \
385                                                                              \
386               if (task->valid && schedule.fd_list.fd[i] != -1) {             \
387                 /* Task ready for writing */                                 \
388                 if ((schedule.fd_list.fd[i] & (1L << SILC_TASK_WRITE)))      \
389                   task->callback(queue, SILC_TASK_WRITE,                     \
390                                  task->context, i);                          \
391               }                                                              \
392                                                                              \
393               if (!task->valid) {                                            \
394                 /* Invalid (unregistered) tasks are removed from the         \
395                    task queue. */                                            \
396                 if (queue->task == task->next) {                             \
397                   silc_task_remove(queue, task);                             \
398                   break;                                                     \
399                 }                                                            \
400                                                                              \
401                 task = task->next;                                           \
402                 silc_task_remove(queue, task->prev);                         \
403                 continue;                                                    \
404               }                                                              \
405                                                                              \
406               /* Break if there isn't more tasks in the queue */             \
407               if (queue->task == task->next)                                 \
408                 break;                                                       \
409                                                                              \
410               task = task->next;                                             \
411             }                                                                \
412           }                                                                  \
413         }                                                                    \
414       }                                                                      \
415   }                                                                          \
416 } while(0)
417
418 /* The SILC scheduler. This is actually the main routine in SILC programs.
419    When this returns the program is to be ended. Before this function can
420    be called, one must call silc_schedule_init function. */
421
422 void silc_schedule()
423 {
424   int is_run, i;
425   SilcTask task;
426   SilcTaskQueue queue;
427   struct timeval curtime;
428
429   SILC_LOG_DEBUG(("Running scheduler"));
430
431   if (schedule.valid == FALSE) {
432     SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
433     return;
434   }
435
436   /* Start the scheduler loop */
437   while(1) {
438
439     SILC_LOG_DEBUG(("In scheduler loop"));
440
441     /* If the task queues aren't initialized or we aren't valid anymore
442        we will return */
443     if ((!schedule.fd_queue && !schedule.timeout_queue 
444          && !schedule.generic_queue) || schedule.valid == FALSE) {
445       SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
446       break;
447     }
448
449     /* Clear everything */
450     FD_ZERO(&schedule.in);
451     FD_ZERO(&schedule.out);
452     schedule.max_fd = -1;
453     is_run = FALSE;
454
455     /* Calculate next timeout for select(). This is the timeout value
456        when at earliest some of the timeout tasks expire. */
457     SILC_SCHEDULE_SELECT_TIMEOUT;
458
459     /* Add the file descriptors to the fd sets. These are the non-timeout
460        tasks. The select() listens to these file descriptors. */
461     SILC_SCHEDULE_SELECT_TASKS;
462
463     if (schedule.max_fd == -1) {
464       SILC_LOG_ERROR(("Nothing to listen, exiting"));
465       break;
466     }
467
468     if (schedule.timeout)
469       SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule.timeout->tv_sec,
470                       schedule.timeout->tv_usec));
471
472     /* This is the main select(). The program blocks here until some
473        of the selected file descriptors change status or the selected
474        timeout expires. */
475     SILC_LOG_DEBUG(("Select"));
476     switch(select(schedule.max_fd + 1, &schedule.in, 
477                   &schedule.out, 0, schedule.timeout)) {
478     case -1:
479       /* Error */
480       SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
481       break;
482     case 0:
483       /* Timeout */
484       SILC_LOG_DEBUG(("Running timeout tasks"));
485       gettimeofday(&curtime, NULL);
486       SILC_SCHEDULE_RUN_TIMEOUT_TASKS;
487       break;
488     default:
489       /* There is some data available now */
490       SILC_LOG_DEBUG(("Running non-timeout tasks"));
491       SILC_SCHEDULE_RUN_TASKS;
492
493       SILC_SCHEDULE_RUN_GENERIC_TASKS;
494       break;
495     }
496   }
497 }