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