updates.
[silc.git] / lib / silcutil / silcschedule.c
1 /*
2
3   silcschedule.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 1998 - 2001 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 /* $Id$ */
21
22 #include "silcincludes.h"
23
24 /* System specific implementation of the select. */
25 int silc_select(int n, fd_set *readfds, fd_set *writefds,
26                 fd_set *exceptfds, struct timeval *timeout);
27
28 /* Structure holding list of file descriptors, scheduler is supposed to
29    be listenning. The max_fd field is the maximum number of possible file
30    descriptors in the list. This value is set at the initialization
31    of the scheduler and it usually is the maximum number of connections 
32    allowed. */
33 typedef struct {
34   int *fd;
35   uint32 last_fd;
36   uint32 max_fd;
37 } SilcScheduleFdList;
38
39 /* 
40    SILC Scheduler structure.
41
42    This is the actual schedule object in SILC. Both SILC client and server 
43    uses this same scheduler. Actually, this scheduler could be used by any
44    program needing scheduling.
45
46    Following short description of the fields:
47
48    SilcTaskQueue fd_queue
49
50        Task queue hook for non-timeout tasks. Usually this means that these
51        tasks perform different kind of I/O on file descriptors. File 
52        descriptors are usually network sockets but they actually can be
53        any file descriptors. This hook is initialized in silc_schedule_init
54        function. Timeout tasks should not be added to this queue because
55        they will never expire.
56
57    SilcTaskQueue timeout_queue
58
59        Task queue hook for timeout tasks. This hook is reserved specificly
60        for tasks with timeout. Non-timeout tasks should not be added to this
61        queue because they will never get scheduled. This hook is also
62        initialized in silc_schedule_init function.
63
64    SilcTaskQueue generic_queue
65
66        Task queue hook for generic tasks. This hook is reserved specificly
67        for generic tasks, tasks that apply to all file descriptors, except
68        to those that have specificly registered a non-timeout task. This hook
69        is also initialized in silc_schedule_init function.
70
71    SilcScheduleFdList fd_list
72
73        List of file descriptors the scheduler is supposed to be listenning.
74        This is updated internally.
75
76    struct timeval *timeout;
77
78        Pointer to the schedules next timeout. Value of this timeout is
79        automatically updated in the silc_schedule function.
80
81    int valid
82
83        Marks validity of the scheduler. This is a boolean value. When this
84        is false the scheduler is terminated and the program will end. This
85        set to true when the scheduler is initialized with silc_schedule_init
86        function.
87
88    fd_set in
89    fd_set out
90
91        File descriptor sets for select(). These are automatically managed
92        by the scheduler and should not be touched otherwise.
93
94    int max_fd
95
96        Number of maximum file descriptors for select(). This, as well, is
97        managed automatically by the scheduler and should be considered to 
98        be read-only field otherwise.
99
100 */
101 struct SilcScheduleStruct {
102   SilcTaskQueue fd_queue;
103   SilcTaskQueue timeout_queue;
104   SilcTaskQueue generic_queue;
105   SilcScheduleFdList fd_list;
106   struct timeval *timeout;
107   bool valid;
108   fd_set in;
109   fd_set out;
110   int max_fd;
111 };
112
113 /* Initializes the scheduler. Sets the non-timeout task queue hook and
114    the timeout task queue hook. This must be called before the scheduler
115    is able to work. This will allocate the queue pointers if they are
116    not allocated. Returns the scheduler context that must be freed by
117    the silc_schedule_uninit function. */
118
119 SilcSchedule silc_schedule_init(SilcTaskQueue *fd_queue,
120                                 SilcTaskQueue *timeout_queue,
121                                 SilcTaskQueue *generic_queue,
122                                 int max_fd)
123 {
124   SilcSchedule schedule;
125   int i;
126
127   SILC_LOG_DEBUG(("Initializing scheduler"));
128
129   schedule = silc_calloc(1, sizeof(*schedule));
130
131   /* Register the task queues if they are not registered already. In SILC
132      we have by default three task queues. One task queue for non-timeout
133      tasks which perform different kind of I/O on file descriptors, timeout
134      task queue for timeout tasks, and, generic non-timeout task queue whose
135      tasks apply to all connections. */
136   if (!*fd_queue)
137     silc_task_queue_alloc(schedule, fd_queue, TRUE);
138   if (!*timeout_queue)
139     silc_task_queue_alloc(schedule, timeout_queue, TRUE);
140   if (!*generic_queue)
141     silc_task_queue_alloc(schedule, generic_queue, TRUE);
142
143   /* Initialize the scheduler */
144   schedule->fd_queue = *fd_queue;
145   schedule->timeout_queue = *timeout_queue;
146   schedule->generic_queue = *generic_queue;
147   schedule->fd_list.fd = silc_calloc(max_fd, sizeof(*schedule->fd_list.fd));
148   schedule->fd_list.last_fd = 0;
149   schedule->fd_list.max_fd = max_fd;
150   schedule->timeout = NULL;
151   schedule->valid = TRUE;
152   FD_ZERO(&schedule->in);
153   FD_ZERO(&schedule->out);
154   schedule->max_fd = -1;
155   for (i = 0; i < max_fd; i++)
156     schedule->fd_list.fd[i] = -1;
157
158   return schedule;
159 }
160
161 /* Uninitializes the schedule. This is called when the program is ready
162    to end. This removes all tasks and task queues. Returns FALSE if the
163    scheduler could not be uninitialized. This happens when the scheduler
164    is still valid and silc_schedule_stop has not been called. */
165
166 bool silc_schedule_uninit(SilcSchedule schedule)
167 {
168
169   SILC_LOG_DEBUG(("Uninitializing scheduler"));
170
171   if (schedule->valid == TRUE)
172     return FALSE;
173
174   /* Unregister all tasks */
175   if (schedule->fd_queue)
176     silc_task_remove(schedule->fd_queue, SILC_ALL_TASKS);
177   if (schedule->timeout_queue)
178     silc_task_remove(schedule->timeout_queue, SILC_ALL_TASKS);
179   if (schedule->generic_queue)
180     silc_task_remove(schedule->generic_queue, SILC_ALL_TASKS);
181
182   /* Unregister all task queues */
183   if (schedule->fd_queue)
184     silc_task_queue_free(schedule->fd_queue);
185   if (schedule->timeout_queue)
186     silc_task_queue_free(schedule->timeout_queue);
187   if (schedule->generic_queue)
188     silc_task_queue_free(schedule->generic_queue);
189
190   /* Clear the fd list */
191   if (schedule->fd_list.fd) {
192     memset(schedule->fd_list.fd, -1, schedule->fd_list.max_fd);
193     silc_free(schedule->fd_list.fd);
194   }
195
196   return TRUE;
197 }
198
199 /* Stops the schedule even if it is not supposed to be stopped yet. 
200    After calling this, one should call silc_schedule_uninit (after the 
201    silc_schedule has returned). */
202
203 void silc_schedule_stop(SilcSchedule schedule)
204 {
205   SILC_LOG_DEBUG(("Stopping scheduler"));
206
207   if (schedule->valid == TRUE)
208     schedule->valid = FALSE;
209 }
210
211 /* Sets a file descriptor to be listened by select() in scheduler. One can
212    call this directly if wanted. This can be called multiple times for
213    one file descriptor to set different iomasks. */
214
215 void silc_schedule_set_listen_fd(SilcSchedule schedule, int fd, uint32 iomask)
216 {
217   schedule->fd_list.fd[fd] = iomask;
218   
219   if (fd > schedule->fd_list.last_fd)
220     schedule->fd_list.last_fd = fd;
221 }
222
223 /* Removes a file descriptor from listen list. */
224
225 void silc_schedule_unset_listen_fd(SilcSchedule schedule, int fd)
226 {
227   schedule->fd_list.fd[fd] = -1;
228   
229   if (fd == schedule->fd_list.last_fd) {
230     int i;
231
232     for (i = fd; i >= 0; i--)
233       if (schedule->fd_list.fd[i] != -1)
234         break;
235
236     schedule->fd_list.last_fd = i < 0 ? 0 : i;
237   }
238 }
239
240 /* Executes tasks matching the file descriptor set by select(). The task
241    remains on the task queue after execution. Invalid tasks are removed 
242    here from the task queue. This macro is used by silc_schedule function. 
243    We don't have to care about the tasks priority here because the tasks
244    are sorted in their priority order already at the registration phase. */
245
246 #define SILC_SCHEDULE_RUN_TASKS                                            \
247 do {                                                                       \
248   queue = schedule->fd_queue;                                              \
249   if (queue && queue->valid == TRUE && queue->task) {                      \
250     task = queue->task;                                                    \
251                                                                            \
252     /* Walk thorugh all tasks in the particular task queue and             \
253        execute the callback functions of those tasks matching the          \
254        fd set by select(). */                                              \
255     while(1) {                                                             \
256       /* Validity of the task is checked always before and after           \
257          execution beacuse the task might have been unregistered           \
258          in the callback function, ie. it is not valid anymore. */         \
259                                                                            \
260       if (task->valid) {                                                   \
261         /* Task ready for reading */                                       \
262         if ((FD_ISSET(task->fd, &schedule->in)) &&                         \
263             (task->iomask & (1L << SILC_TASK_READ))) {                     \
264           task->callback(queue, SILC_TASK_READ, task->context, task->fd);  \
265           is_run = TRUE;                                                   \
266         }                                                                  \
267       }                                                                    \
268                                                                            \
269       if (task->valid) {                                                   \
270         /* Task ready for writing */                                       \
271         if ((FD_ISSET(task->fd, &schedule->out)) &&                        \
272             (task->iomask & (1L << SILC_TASK_WRITE))) {                    \
273           task->callback(queue, SILC_TASK_WRITE, task->context, task->fd); \
274           is_run = TRUE;                                                   \
275         }                                                                  \
276       }                                                                    \
277                                                                            \
278       if (!task->valid) {                                                  \
279         /* Invalid (unregistered) tasks are removed from the               \
280            task queue. */                                                  \
281         if (queue->task == task->next) {                                   \
282           silc_task_remove(queue, task);                                   \
283           break;                                                           \
284         }                                                                  \
285                                                                            \
286         task = task->next;                                                 \
287         silc_task_remove(queue, task->prev);                               \
288         continue;                                                          \
289       }                                                                    \
290                                                                            \
291       /* Break if there isn't more tasks in the queue */                   \
292       if (queue->task == task->next)                                       \
293         break;                                                             \
294                                                                            \
295       task = task->next;                                                   \
296     }                                                                      \
297   }                                                                        \
298 } while(0)
299
300 /* Selects tasks to be listened by select(). These are the non-timeout
301    tasks. This checks the scheduler's fd list. This macro is used by 
302    silc_schedule function. */
303
304 #define SILC_SCHEDULE_SELECT_TASKS                              \
305 do {                                                            \
306   for (i = 0; i <= schedule->fd_list.last_fd; i++) {            \
307     if (schedule->fd_list.fd[i] != -1) {                                \
308                                                                 \
309       /* Set the max fd value for select() to listen */         \
310       if (i > schedule->max_fd)                                 \
311         schedule->max_fd = i;                                   \
312                                                                 \
313       /* Add tasks for reading */                               \
314       if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_READ)))   \
315         FD_SET(i, &schedule->in);                               \
316                                                                 \
317       /* Add tasks for writing */                               \
318       if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_WRITE)))  \
319         FD_SET(i, &schedule->out);                              \
320     }                                                           \
321   }                                                             \
322 } while(0)
323
324 /* Executes all tasks whose timeout has expired. The task is removed from
325    the task queue after the callback function has returned. Also, invalid
326    tasks are removed here. The current time must be get before calling this
327    macro. This macro is used by silc_schedule function. We don't have to
328    care about priorities because tasks are already sorted in their priority
329    order at the registration phase. */
330
331 #define SILC_SCHEDULE_RUN_TIMEOUT_TASKS                                 \
332 do {                                                                    \
333   queue = schedule->timeout_queue;                                      \
334   if (queue && queue->valid == TRUE && queue->task) {                   \
335     task = queue->task;                                                 \
336                                                                         \
337     /* Walk thorugh all tasks in the particular task queue              \
338        and run all the expired tasks. */                                \
339     while(1) {                                                          \
340       /* Execute the task if the timeout has expired */                 \
341       if (silc_task_timeout_compare(&task->timeout, &curtime)) {        \
342                                                                         \
343         /* Task ready for reading */                                    \
344         if (task->valid) {                                              \
345           if ((task->iomask & (1L << SILC_TASK_READ)))                  \
346             task->callback(queue, SILC_TASK_READ,                       \
347                            task->context, task->fd);                    \
348         }                                                               \
349                                                                         \
350         /* Task ready for writing */                                    \
351         if (task->valid) {                                              \
352           if ((task->iomask & (1L << SILC_TASK_WRITE)))                 \
353             task->callback(queue, SILC_TASK_WRITE,                      \
354                            task->context, task->fd);                    \
355         }                                                               \
356                                                                         \
357         /* Break if there isn't more tasks in the queue */              \
358         if (queue->task == task->next) {                                \
359           /* Remove the task from queue */                              \
360           silc_task_remove(queue, task);                                \
361           break;                                                        \
362         }                                                               \
363                                                                         \
364         task = task->next;                                              \
365                                                                         \
366         /* Remove the task from queue */                                \
367         silc_task_remove(queue, task->prev);                            \
368       } else {                                                          \
369         /* The timeout hasn't expired, check for next one */            \
370                                                                         \
371         /* Break if there isn't more tasks in the queue */              \
372         if (queue->task == task->next)                                  \
373           break;                                                        \
374                                                                         \
375         task = task->next;                                              \
376       }                                                                 \
377     }                                                                   \
378   }                                                                     \
379 } while(0)
380
381 /* Calculates next timeout for select(). This is the timeout value
382    when at earliest some of the timeout tasks expire. If this is in the
383    past, they will be run now. This macro is used by the silc_schedule
384    function. */
385
386 #define SILC_SCHEDULE_SELECT_TIMEOUT                                        \
387 do {                                                                        \
388   if (schedule->timeout_queue && schedule->timeout_queue->valid == TRUE) {    \
389     queue = schedule->timeout_queue;                                        \
390     task = NULL;                                                            \
391                                                                             \
392     /* Get the current time */                                              \
393     silc_gettimeofday(&curtime);                                            \
394     schedule->timeout = NULL;                                               \
395                                                                             \
396     /* First task in the task queue has always the smallest timeout. */     \
397     task = queue->task;                                                     \
398     while(1) {                                                              \
399       if (task && task->valid == TRUE) {                                    \
400                                                                             \
401         /* If the timeout is in past, we will run the task and all other    \
402            timeout tasks from the past. */                                  \
403         if (silc_task_timeout_compare(&task->timeout, &curtime)) {          \
404           SILC_SCHEDULE_RUN_TIMEOUT_TASKS;                                  \
405                                                                             \
406           /* The task(s) has expired and doesn't exist on the task queue    \
407              anymore. We continue with new timeout. */                      \
408           queue = schedule->timeout_queue;                                  \
409           task = queue->task;                                               \
410           if (task == NULL || task->valid == FALSE)                         \
411             break;                                                          \
412           goto cont;                                                        \
413         } else {                                                            \
414  cont:                                                                      \
415           /* Calculate the next timeout for select() */                     \
416           queue->timeout.tv_sec = task->timeout.tv_sec - curtime.tv_sec;    \
417           queue->timeout.tv_usec = task->timeout.tv_usec - curtime.tv_usec; \
418           if (queue->timeout.tv_sec < 0)                                    \
419             queue->timeout.tv_sec = 0;                                      \
420                                                                             \
421           /* We wouldn't want to go under zero, check for it. */            \
422           if (queue->timeout.tv_usec < 0) {                                 \
423             queue->timeout.tv_sec -= 1;                                     \
424             if (queue->timeout.tv_sec < 0)                                  \
425               queue->timeout.tv_sec = 0;                                    \
426             queue->timeout.tv_usec += 1000000L;                             \
427           }                                                                 \
428         }                                                                   \
429         /* We've got the timeout value */                                   \
430         break;                                                              \
431       } else {                                                              \
432         /* Task is not valid, remove it and try next one. */                \
433         silc_task_remove(queue, task);                                      \
434         task = queue->task;                                                 \
435         if (queue->task == NULL)                                            \
436           break;                                                            \
437       }                                                                     \
438     }                                                                       \
439     /* Save the timeout */                                                  \
440     if (task)                                                               \
441       schedule->timeout = &queue->timeout;                                  \
442   }                                                                         \
443 } while(0)
444
445 /* Execute generic tasks. These are executed only and only if for the
446    specific fd there wasn't other non-timeout tasks. This checks the earlier
447    set fd list, thus the generic tasks apply to all specified fd's. All the
448    generic tasks are executed at once. */
449
450 #define SILC_SCHEDULE_RUN_GENERIC_TASKS                                      \
451 do {                                                                         \
452   if (is_run == FALSE) {                                                     \
453     SILC_LOG_DEBUG(("Running generic tasks"));                               \
454     for (i = 0; i <= schedule->fd_list.last_fd; i++)                         \
455       if (schedule->fd_list.fd[i] != -1) {                                   \
456                                                                              \
457         /* Check whether this fd is select()ed. */                           \
458         if ((FD_ISSET(i, &schedule->in)) || (FD_ISSET(i, &schedule->out))) {   \
459                                                                              \
460           /* It was selected. Now find the tasks from task queue and execute \
461              all generic tasks. */                                           \
462           if (schedule->generic_queue && schedule->generic_queue->valid) {     \
463             queue = schedule->generic_queue;                                 \
464                                                                              \
465             if (!queue->task)                                                \
466               break;                                                         \
467                                                                              \
468             task = queue->task;                                              \
469                                                                              \
470             while(1) {                                                       \
471               /* Validity of the task is checked always before and after     \
472                  execution beacuse the task might have been unregistered     \
473                  in the callback function, ie. it is not valid anymore. */   \
474                                                                              \
475               if (task->valid && schedule->fd_list.fd[i] != -1) {            \
476                 /* Task ready for reading */                                 \
477                 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_READ)))      \
478                   task->callback(queue, SILC_TASK_READ,                      \
479                                  task->context, i);                          \
480               }                                                              \
481                                                                              \
482               if (task->valid && schedule->fd_list.fd[i] != -1) {            \
483                 /* Task ready for writing */                                 \
484                 if ((schedule->fd_list.fd[i] & (1L << SILC_TASK_WRITE)))             \
485                   task->callback(queue, SILC_TASK_WRITE,                     \
486                                  task->context, i);                          \
487               }                                                              \
488                                                                              \
489               if (!task->valid) {                                            \
490                 /* Invalid (unregistered) tasks are removed from the         \
491                    task queue. */                                            \
492                 if (queue->task == task->next) {                             \
493                   silc_task_remove(queue, task);                             \
494                   break;                                                     \
495                 }                                                            \
496                                                                              \
497                 task = task->next;                                           \
498                 silc_task_remove(queue, task->prev);                         \
499                 continue;                                                    \
500               }                                                              \
501                                                                              \
502               /* Break if there isn't more tasks in the queue */             \
503               if (queue->task == task->next)                                 \
504                 break;                                                       \
505                                                                              \
506               task = task->next;                                             \
507             }                                                                \
508           }                                                                  \
509         }                                                                    \
510       }                                                                      \
511   }                                                                          \
512 } while(0)
513
514 bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
515 {
516   struct timeval timeout;
517   int is_run, i;
518   SilcTask task;
519   SilcTaskQueue queue;
520   struct timeval curtime;
521
522   SILC_LOG_DEBUG(("In scheduler loop"));
523
524   /* If the task queues aren't initialized or we aren't valid anymore
525      we will return */
526   if ((!schedule->fd_queue && !schedule->timeout_queue 
527        && !schedule->generic_queue) || schedule->valid == FALSE) {
528     SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
529     return FALSE;
530   }
531
532   /* Clear everything */
533   FD_ZERO(&schedule->in);
534   FD_ZERO(&schedule->out);
535   schedule->max_fd = -1;
536   is_run = FALSE;
537
538   /* Calculate next timeout for select(). This is the timeout value
539      when at earliest some of the timeout tasks expire. */
540   SILC_SCHEDULE_SELECT_TIMEOUT;
541
542   /* Add the file descriptors to the fd sets. These are the non-timeout
543      tasks. The select() listens to these file descriptors. */
544   SILC_SCHEDULE_SELECT_TASKS;
545
546   if (schedule->max_fd == -1 && !schedule->timeout)
547     return FALSE;
548
549   if (schedule->timeout) {
550     SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout->tv_sec,
551                     schedule->timeout->tv_usec));
552   }
553
554   if (timeout_usecs >= 0) {
555     timeout.tv_sec = 0;
556     timeout.tv_usec = timeout_usecs;
557     schedule->timeout = &timeout;
558   }
559
560   /* This is the main select(). The program blocks here until some
561      of the selected file descriptors change status or the selected
562      timeout expires. */
563   SILC_LOG_DEBUG(("Select"));
564   switch (silc_select(schedule->max_fd + 1, &schedule->in,
565                       &schedule->out, 0, schedule->timeout)) {
566   case -1:
567     /* Error */
568     if (errno == EINTR)
569       break;
570     SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
571     break;
572   case 0:
573     /* Timeout */
574     SILC_LOG_DEBUG(("Running timeout tasks"));
575     silc_gettimeofday(&curtime);
576     SILC_SCHEDULE_RUN_TIMEOUT_TASKS;
577     break;
578   default:
579     /* There is some data available now */
580     SILC_LOG_DEBUG(("Running non-timeout tasks"));
581     SILC_SCHEDULE_RUN_TASKS;
582
583     SILC_SCHEDULE_RUN_GENERIC_TASKS;
584     break;
585   }
586
587   return TRUE;
588 }
589
590 /* The SILC scheduler. This is actually the main routine in SILC programs.
591    When this returns the program is to be ended. Before this function can
592    be called, one must call silc_schedule_init function. */
593
594 void silc_schedule(SilcSchedule schedule)
595 {
596   SILC_LOG_DEBUG(("Running scheduler"));
597
598   if (schedule->valid == FALSE) {
599     SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
600     return;
601   }
602
603   /* Start the scheduler loop */
604   while (silc_schedule_one(schedule, -1)) 
605     ;
606 }