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