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