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