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 #include "silcschedule_i.h"
24
25 /* Forward declarations */
26 typedef struct SilcTaskQueueStruct *SilcTaskQueue;
27
28 /* System specific routines. Implemented under unix/ and win32/. */
29
30 /* System specific select(). Returns same values as normal select(). */
31 int silc_select(SilcScheduleFd fds, uint32 fds_count, 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(SilcSchedule schedule);
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
48 /* Internal task management routines. */
49
50 static void silc_task_queue_alloc(SilcTaskQueue *queue);
51 static void silc_task_queue_free(SilcTaskQueue queue);
52 static SilcTask silc_task_find(SilcTaskQueue queue, uint32 fd);
53 static SilcTask silc_task_add(SilcTaskQueue queue, SilcTask newtask, 
54                               SilcTaskPriority priority);
55 static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first);
56 static SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask newtask,
57                                       SilcTaskPriority priority);
58 static int silc_schedule_task_remove(SilcTaskQueue queue, SilcTask task);
59 static int silc_schedule_task_timeout_compare(struct timeval *smaller, 
60                                               struct timeval *bigger);
61 static void silc_task_del_by_context(SilcTaskQueue queue, void *context);
62 static void silc_task_del_by_callback(SilcTaskQueue queue,
63                                       SilcTaskCallback callback);
64 static void silc_task_del_by_fd(SilcTaskQueue queue, uint32 fd);
65
66 /* Returns the task queue by task type */
67 #define SILC_SCHEDULE_GET_QUEUE(type)                                   \
68   (type == SILC_TASK_FD ? schedule->fd_queue :                          \
69    type == SILC_TASK_TIMEOUT ? schedule->timeout_queue :                \
70    schedule->generic_queue)
71
72 /* SILC Task object. Represents one task in the scheduler. */
73 struct SilcTaskStruct {
74   uint32 fd;
75   struct timeval timeout;
76   SilcTaskCallback callback;
77   void *context;
78   bool valid;
79   SilcTaskPriority priority;
80   SilcTaskType type;
81
82   /* Pointers forming doubly linked circular list */
83   struct SilcTaskStruct *next;
84   struct SilcTaskStruct *prev;
85 };
86
87 /* SILC Task Queue object. The queue holds all the tasks in the scheduler.
88    There are always three task queues in the scheduler. One for non-timeout
89    tasks (fd tasks performing tasks over specified file descriptor), 
90    one for timeout tasks and one for generic tasks. */
91 struct SilcTaskQueueStruct {
92   SilcTask task;                /* Pointer to all tasks */
93   struct timeval timeout;       /* Current timeout */
94   SILC_MUTEX_DEFINE(lock);      /* Queue's lock */
95 };
96
97 /* 
98    SILC Scheduler structure.
99
100    This is the actual schedule object in SILC. Both SILC client and server 
101    uses this same scheduler. Actually, this scheduler could be used by any
102    program needing scheduling.
103
104    Following short description of the fields:
105
106    SilcTaskQueue fd_queue
107
108        Task queue hook for non-timeout tasks. Usually this means that these
109        tasks perform different kind of I/O on file descriptors. File 
110        descriptors are usually network sockets but they actually can be
111        any file descriptors. This hook is initialized in silc_schedule_init
112        function. Timeout tasks should not be added to this queue because
113        they will never expire.
114
115    SilcTaskQueue timeout_queue
116
117        Task queue hook for timeout tasks. This hook is reserved specificly
118        for tasks with timeout. Non-timeout tasks should not be added to this
119        queue because they will never get scheduled. This hook is also
120        initialized in silc_schedule_init function.
121
122    SilcTaskQueue generic_queue
123
124        Task queue hook for generic tasks. This hook is reserved specificly
125        for generic tasks, tasks that apply to all file descriptors, except
126        to those that have specificly registered a non-timeout task. This hook
127        is also initialized in silc_schedule_init function.
128
129    SilcScheduleFd fd_list
130
131        List of file descriptors the scheduler is supposed to be listenning.
132        This is updated internally.
133
134    uint32 max_fd
135    uint32 last_fd
136
137        Size of the fd_list list. There can be `max_fd' many tasks in
138        the scheduler at once. The `last_fd' is the last valid entry
139        in the fd_list.
140
141    struct timeval *timeout;
142
143        Pointer to the schedules next timeout. Value of this timeout is
144        automatically updated in the silc_schedule function.
145
146    bool valid
147
148        Marks validity of the scheduler. This is a boolean value. When this
149        is false the scheduler is terminated and the program will end. This
150        set to true when the scheduler is initialized with silc_schedule_init
151        function.
152
153    fd_set in
154    fd_set out
155
156        File descriptor sets for select(). These are automatically managed
157        by the scheduler and should not be touched otherwise.
158
159    void *wakeup
160
161        System specific wakeup context. On multi-threaded environments the
162        scheduler needs to be wakenup (in the thread) when tasks are added
163        or removed. This is initialized by silc_schedule_wakeup_init.
164
165    SILC_MUTEX_DEFINE(lock)
166   
167        Scheduler lock.
168
169 */
170 struct SilcScheduleStruct {
171   SilcTaskQueue fd_queue;
172   SilcTaskQueue timeout_queue;
173   SilcTaskQueue generic_queue;
174   SilcScheduleFd fd_list;
175   uint32 max_fd;
176   uint32 last_fd;
177   struct timeval *timeout;
178   bool valid;
179   void *wakeup;
180   SILC_MUTEX_DEFINE(lock);
181   bool is_locked;
182 };
183
184 /* Initializes the scheduler. This returns the scheduler context that
185    is given as arugment usually to all silc_schedule_* functions.
186    The `max_tasks' indicates the number of maximum tasks that the
187    scheduler can handle. */
188
189 SilcSchedule silc_schedule_init(int max_tasks)
190 {
191   SilcSchedule schedule;
192
193   SILC_LOG_DEBUG(("Initializing scheduler"));
194
195   schedule = silc_calloc(1, sizeof(*schedule));
196
197   /* Allocate three task queues, one for file descriptor based tasks,
198      one for timeout tasks and one for generic tasks. */
199   silc_task_queue_alloc(&schedule->fd_queue);
200   silc_task_queue_alloc(&schedule->timeout_queue);
201   silc_task_queue_alloc(&schedule->generic_queue);
202
203   /* Initialize the scheduler */
204   schedule->fd_list = silc_calloc(max_tasks, sizeof(*schedule->fd_list));
205   schedule->max_fd = max_tasks;
206   schedule->timeout = NULL;
207   schedule->valid = TRUE;
208
209   /* Allocate scheduler lock */
210   silc_mutex_alloc(&schedule->lock);
211
212   /* Initialize the wakeup, for multi-threads support */
213   schedule->wakeup = silc_schedule_wakeup_init(schedule);
214
215   return schedule;
216 }
217
218 /* Uninitializes the schedule. This is called when the program is ready
219    to end. This removes all tasks and task queues. Returns FALSE if the
220    scheduler could not be uninitialized. This happens when the scheduler
221    is still valid and silc_schedule_stop has not been called. */
222
223 bool silc_schedule_uninit(SilcSchedule schedule)
224 {
225   SILC_LOG_DEBUG(("Uninitializing scheduler"));
226
227   if (schedule->valid == TRUE)
228     return FALSE;
229
230   /* Unregister all tasks */
231   silc_schedule_task_remove(schedule->fd_queue, SILC_ALL_TASKS);
232   silc_schedule_task_remove(schedule->timeout_queue, SILC_ALL_TASKS);
233   silc_schedule_task_remove(schedule->generic_queue, SILC_ALL_TASKS);
234
235   /* Unregister all task queues */
236   silc_task_queue_free(schedule->fd_queue);
237   silc_task_queue_free(schedule->timeout_queue);
238   silc_task_queue_free(schedule->generic_queue);
239
240   silc_free(schedule->fd_list);
241
242   /* Uninit the wakeup */
243   silc_schedule_wakeup_uninit(schedule->wakeup);
244
245   silc_mutex_free(schedule->lock);
246
247   return TRUE;
248 }
249
250 /* Stops the schedule even if it is not supposed to be stopped yet. 
251    After calling this, one should call silc_schedule_uninit (after the 
252    silc_schedule has returned). */
253
254 void silc_schedule_stop(SilcSchedule schedule)
255 {
256   SILC_LOG_DEBUG(("Stopping scheduler"));
257   silc_mutex_lock(schedule->lock);
258   schedule->valid = FALSE;
259   silc_mutex_unlock(schedule->lock);
260 }
261
262 /* Executes nontimeout tasks. It then checks whether any of ther fd tasks
263    was signaled by the silc_select. If some task was not signaled then
264    all generic tasks are executed for that task. The generic tasks are
265    never executed for task that has explicit fd task set. */
266 /* This holds the schedule->lock and the queue locks. */
267
268 static void silc_schedule_dispatch_nontimeout(SilcSchedule schedule)
269 {
270   SilcTask task;
271   int i, last_fd = schedule->last_fd;
272
273   for (i = 0; i <= last_fd; i++) {
274     if (schedule->fd_list[i].events == 0)
275       continue;
276
277     /* First check whether this fd has task in the fd queue */
278     silc_mutex_lock(schedule->fd_queue->lock);
279     task = silc_task_find(schedule->fd_queue, schedule->fd_list[i].fd);
280     silc_mutex_unlock(schedule->fd_queue->lock);
281
282     /* If the task was found then execute its callbacks. If not then
283        execute all generic tasks for that fd. */
284     if (task) {
285       /* Validity of the task is checked always before and after
286          execution beacuse the task might have been unregistered
287          in the callback function, ie. it is not valid anymore. */
288       silc_mutex_lock(schedule->fd_queue->lock);
289
290       /* Is the task ready for reading */
291       if (task->valid && schedule->fd_list[i].revents & SILC_TASK_READ) {
292         silc_mutex_unlock(schedule->fd_queue->lock);
293         silc_mutex_unlock(schedule->lock);
294         task->callback(schedule, SILC_TASK_READ, task->fd, task->context);
295         silc_mutex_lock(schedule->lock);
296         silc_mutex_lock(schedule->fd_queue->lock);
297       }
298
299       /* Is the task ready for writing */
300       if (task->valid && schedule->fd_list[i].revents & SILC_TASK_WRITE) {
301         silc_mutex_unlock(schedule->fd_queue->lock);
302         silc_mutex_unlock(schedule->lock);
303         task->callback(schedule, SILC_TASK_WRITE, task->fd, task->context);
304         silc_mutex_lock(schedule->lock);
305         silc_mutex_lock(schedule->fd_queue->lock);
306       }
307
308       if (!task->valid)
309         silc_schedule_task_remove(schedule->fd_queue, task);
310
311       silc_mutex_unlock(schedule->fd_queue->lock);
312     } else {
313       /* Run generic tasks for this fd. */
314
315       silc_mutex_lock(schedule->generic_queue->lock);
316       if (!schedule->generic_queue->task) {
317         silc_mutex_unlock(schedule->generic_queue->lock);
318         continue;
319       }
320
321       task = schedule->generic_queue->task;
322       while(1) {
323         /* Validity of the task is checked always before and after
324            execution beacuse the task might have been unregistered
325            in the callback function, ie. it is not valid anymore. */
326
327         /* Is the task ready for reading */                             
328         if (task->valid && schedule->fd_list[i].revents & SILC_TASK_READ) {
329           silc_mutex_unlock(schedule->generic_queue->lock);
330           silc_mutex_unlock(schedule->lock);
331           task->callback(schedule, SILC_TASK_READ, schedule->fd_list[i].fd, 
332                          task->context);
333           silc_mutex_lock(schedule->lock);
334           silc_mutex_lock(schedule->generic_queue->lock);
335         }
336
337         /* Is the task ready for writing */                             
338         if (task->valid && schedule->fd_list[i].revents & SILC_TASK_WRITE) {
339           silc_mutex_unlock(schedule->generic_queue->lock);
340           silc_mutex_unlock(schedule->lock);
341           task->callback(schedule, SILC_TASK_WRITE, schedule->fd_list[i].fd, 
342                          task->context);
343           silc_mutex_lock(schedule->lock);
344           silc_mutex_lock(schedule->generic_queue->lock);
345         }
346
347         if (!task->valid) {
348           /* Invalid (unregistered) tasks are removed from the
349              task queue. */
350           if (schedule->generic_queue->task == task->next) {
351             silc_schedule_task_remove(schedule->generic_queue, task);
352             silc_mutex_unlock(schedule->generic_queue->lock);
353             break;
354           }
355
356           task = task->next;
357           silc_schedule_task_remove(schedule->generic_queue, task);
358           continue;
359         }
360
361         /* Break if there isn't more tasks in the queue */
362         if (schedule->generic_queue->task == task->next)
363           break;
364
365         task = task->next;
366       }                 
367
368       silc_mutex_unlock(schedule->generic_queue->lock);
369     }
370   }
371 }
372
373 /* Executes all tasks whose timeout has expired. The task is removed from
374    the task queue after the callback function has returned. Also, invalid
375    tasks are removed here. We don't have to care about priorities because 
376    tasks are already sorted in their priority order at the registration 
377    phase. */
378 /* This holds the schedule->lock and the schedule->timeout_queue->lock */
379
380 static void silc_schedule_dispatch_timeout(SilcSchedule schedule)
381 {
382   SilcTaskQueue queue = schedule->timeout_queue;
383   SilcTask task;
384   struct timeval curtime;
385
386   SILC_LOG_DEBUG(("Running timeout tasks"));
387
388   silc_gettimeofday(&curtime);
389
390   queue = schedule->timeout_queue;
391   if (queue && queue->task) {
392     task = queue->task;
393
394     /* Walk thorugh all tasks in the particular task queue and run all 
395        the expired tasks. */
396     while(1) {
397       /* Execute the task if the timeout has expired */
398       if (silc_schedule_task_timeout_compare(&task->timeout, &curtime)) {
399         if (task->valid) {
400           silc_mutex_unlock(queue->lock);
401           silc_mutex_unlock(schedule->lock);
402           task->callback(schedule, SILC_TASK_EXPIRE, task->fd, task->context);
403           silc_mutex_lock(schedule->lock);
404           silc_mutex_lock(queue->lock);
405         }
406
407         /* Break if there isn't more tasks in the queue */
408         if (queue->task == task->next) {
409           silc_schedule_task_remove(queue, task);
410           break;
411         }
412
413         task = task->next;
414
415         /* Remove the task from queue */
416         silc_schedule_task_remove(queue, task->prev);
417       } else {
418         /* The timeout hasn't expired, check for next one */
419
420         /* Break if there isn't more tasks in the queue */
421         if (queue->task == task->next)
422           break;
423
424         task = task->next;
425       }
426     }
427   }
428 }
429
430 /* Calculates next timeout for select(). This is the timeout value
431    when at earliest some of the timeout tasks expire. If this is in the
432    past, they will be run now. */
433 /* This holds the schedule->lock and the schedule->timeout_queue->lock */
434
435 static void silc_schedule_select_timeout(SilcSchedule schedule)
436 {
437   SilcTaskQueue queue = schedule->timeout_queue;
438   SilcTask task;
439   struct timeval curtime;
440
441   /* Get the current time */
442   silc_gettimeofday(&curtime);
443   schedule->timeout = NULL;
444
445   /* First task in the task queue has always the smallest timeout. */
446   task = queue->task;
447   while(1) {
448     if (task && task->valid == TRUE) {
449       /* If the timeout is in past, we will run the task and all other
450          timeout tasks from the past. */
451       if (silc_schedule_task_timeout_compare(&task->timeout, &curtime)) {
452         silc_schedule_dispatch_timeout(schedule);
453                                                 
454         /* The task(s) has expired and doesn't exist on the task queue
455            anymore. We continue with new timeout. */
456         queue = schedule->timeout_queue;
457         task = queue->task;
458         if (task == NULL || task->valid == FALSE)
459           break;
460       }
461
462       /* Calculate the next timeout for select() */
463       queue->timeout.tv_sec = task->timeout.tv_sec - curtime.tv_sec;
464       queue->timeout.tv_usec = task->timeout.tv_usec - curtime.tv_usec;
465       if (queue->timeout.tv_sec < 0)
466         queue->timeout.tv_sec = 0;
467
468       /* We wouldn't want to go under zero, check for it. */
469       if (queue->timeout.tv_usec < 0) {
470         queue->timeout.tv_sec -= 1;
471         if (queue->timeout.tv_sec < 0)
472           queue->timeout.tv_sec = 0;
473         queue->timeout.tv_usec += 1000000L;
474       }
475
476       /* We've got the timeout value */
477       break;
478     } else {
479       /* Task is not valid, remove it and try next one. */
480       silc_schedule_task_remove(queue, task);
481       task = queue->task;
482       if (queue->task == NULL)
483         break;
484     }
485   }
486
487   /* Save the timeout */
488   if (task) {
489     schedule->timeout = &queue->timeout;
490     SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout->tv_sec,
491                     schedule->timeout->tv_usec));
492   }
493 }
494
495 /* Runs the scheduler once and then returns. */
496
497 bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
498 {
499   struct timeval timeout;
500   int ret;
501
502   SILC_LOG_DEBUG(("In scheduler loop"));
503
504   if (!schedule->is_locked)
505     silc_mutex_lock(schedule->lock);
506
507   /* If the task queues aren't initialized or we aren't valid anymore
508      we will return */
509   if ((!schedule->fd_queue && !schedule->timeout_queue 
510        && !schedule->generic_queue) || schedule->valid == FALSE) {
511     SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
512     if (!schedule->is_locked)
513       silc_mutex_unlock(schedule->lock);
514     return FALSE;
515   }
516
517   /* Calculate next timeout for silc_select(). This is the timeout value
518      when at earliest some of the timeout tasks expire. */
519   silc_mutex_lock(schedule->timeout_queue->lock);
520   silc_schedule_select_timeout(schedule);
521   silc_mutex_unlock(schedule->timeout_queue->lock);
522
523   if (timeout_usecs >= 0) {
524     timeout.tv_sec = 0;
525     timeout.tv_usec = timeout_usecs;
526     schedule->timeout = &timeout;
527   }
528
529   silc_mutex_unlock(schedule->lock);
530
531   /* This is the main select(). The program blocks here until some
532      of the selected file descriptors change status or the selected
533      timeout expires. */
534   SILC_LOG_DEBUG(("Select"));
535   ret = silc_select(schedule->fd_list, schedule->last_fd + 1, 
536                     schedule->timeout);
537
538   silc_mutex_lock(schedule->lock);
539
540   switch (ret) {
541   case -1:
542     /* Error */
543     if (errno == EINTR)
544       break;
545     SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
546     break;
547   case 0:
548     /* Timeout */
549     silc_mutex_lock(schedule->timeout_queue->lock);
550     silc_schedule_dispatch_timeout(schedule);
551     silc_mutex_unlock(schedule->timeout_queue->lock);
552     break;
553   default:
554     /* There is some data available now */
555     SILC_LOG_DEBUG(("Running non-timeout tasks"));
556     silc_schedule_dispatch_nontimeout(schedule);
557     break;
558   }
559
560   if (!schedule->is_locked)
561     silc_mutex_unlock(schedule->lock);
562
563   return TRUE;
564 }
565
566 /* The SILC scheduler. This is actually the main routine in SILC programs.
567    When this returns the program is to be ended. Before this function can
568    be called, one must call silc_schedule_init function. */
569
570 void silc_schedule(SilcSchedule schedule)
571 {
572   SILC_LOG_DEBUG(("Running scheduler"));
573
574   if (schedule->valid == FALSE) {
575     SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
576     return;
577   }
578
579   silc_mutex_lock(schedule->lock);
580   schedule->is_locked = TRUE;
581
582   /* Start the scheduler loop */
583   while (silc_schedule_one(schedule, -1)) 
584     ;
585
586   silc_mutex_unlock(schedule->lock);
587 }
588
589 /* Wakes up the scheduler. This is used only in multi-threaded
590    environments where threads may add new tasks or remove old tasks
591    from task queues. This is called to wake up the scheduler in the
592    main thread so that it detects the changes in the task queues.
593    If threads support is not compiled in this function has no effect.
594    Implementation of this function is platform specific. */
595
596 void silc_schedule_wakeup(SilcSchedule schedule)
597 {
598 #ifdef SILC_THREADS
599   SILC_LOG_DEBUG(("Wakeup scheduler"));
600   silc_mutex_lock(schedule->lock);
601   silc_schedule_wakeup_internal(schedule->wakeup);
602   silc_mutex_unlock(schedule->lock);
603 #endif
604 }
605
606 /* Add new task to the scheduler */
607
608 SilcTask silc_schedule_task_add(SilcSchedule schedule, uint32 fd,
609                                 SilcTaskCallback callback, void *context, 
610                                 long seconds, long useconds, 
611                                 SilcTaskType type, 
612                                 SilcTaskPriority priority)
613 {
614   SilcTask newtask;
615   SilcTaskQueue queue;
616   int timeout = FALSE;
617
618   SILC_LOG_DEBUG(("Registering new task, fd=%d type=%d priority=%d", fd, 
619                   type, priority));
620
621   queue = SILC_SCHEDULE_GET_QUEUE(type);
622     
623   /* If the task is generic task, we check whether this task has already
624      been registered. Generic tasks are registered only once and after that
625      the same task applies to all file descriptors to be registered. */
626   if (type == SILC_TASK_GENERIC) {
627     silc_mutex_lock(queue->lock);
628
629     if (queue->task) {
630       SilcTask task = queue->task;
631       while(1) {
632         if ((task->callback == callback) && (task->context == context)) {
633           SILC_LOG_DEBUG(("Found matching generic task, using the match"));
634           
635           silc_mutex_unlock(queue->lock);
636
637           /* Add the fd to be listened, the task found now applies to this
638              fd as well. */
639           silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ);
640           return task;
641         }
642         
643         if (queue->task == task->next)
644           break;
645         
646         task = task->next;
647       }
648     }
649
650     silc_mutex_unlock(queue->lock);
651   }
652
653   newtask = silc_calloc(1, sizeof(*newtask));
654   newtask->fd = fd;
655   newtask->context = context;
656   newtask->callback = callback;
657   newtask->valid = TRUE;
658   newtask->priority = priority;
659   newtask->type = type;
660   newtask->next = newtask;
661   newtask->prev = newtask;
662
663   /* Create timeout if marked to be timeout task */
664   if (((seconds + useconds) > 0) && (type == SILC_TASK_TIMEOUT)) {
665     silc_gettimeofday(&newtask->timeout);
666     newtask->timeout.tv_sec += seconds + (useconds / 1000000L);
667     newtask->timeout.tv_usec += (useconds % 1000000L);
668     if (newtask->timeout.tv_usec > 999999L) {
669       newtask->timeout.tv_sec += 1;
670       newtask->timeout.tv_usec -= 1000000L;
671     }
672     timeout = TRUE;
673   }
674
675   /* If the task is non-timeout task we have to tell the scheduler that we
676      would like to have these tasks scheduled at some odd distant future. */
677   if (type != SILC_TASK_TIMEOUT)
678     silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ);
679
680   silc_mutex_lock(queue->lock);
681
682   /* Is this first task of the queue? */
683   if (queue->task == NULL) {
684     queue->task = newtask;
685     silc_mutex_unlock(queue->lock);
686     return newtask;
687   }
688
689   if (timeout)
690     newtask = silc_task_add_timeout(queue, newtask, priority);
691   else
692     newtask = silc_task_add(queue, newtask, priority);
693
694   silc_mutex_unlock(queue->lock);
695
696   return newtask;
697 }
698
699 /* Removes a task from the scheduler */
700
701 void silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
702 {
703   SilcTaskQueue queue = SILC_SCHEDULE_GET_QUEUE(task->type);
704
705   /* Unregister all tasks */
706   if (task == SILC_ALL_TASKS) {
707     SilcTask next;
708     SILC_LOG_DEBUG(("Unregistering all tasks at once"));
709
710     silc_mutex_lock(queue->lock);
711
712     if (!queue->task) {
713       silc_mutex_unlock(queue->lock);
714       return;
715     }
716
717     next = queue->task;
718     
719     while(1) {
720       if (next->valid)
721         next->valid = FALSE;
722       if (queue->task == next->next)
723         break;
724       next = next->next;
725     }
726
727     silc_mutex_unlock(queue->lock);
728     return;
729   }
730
731   SILC_LOG_DEBUG(("Unregistering task"));
732
733   silc_mutex_lock(queue->lock);
734
735   /* Unregister the specific task */
736   if (task->valid)
737     task->valid = FALSE;
738
739   silc_mutex_unlock(queue->lock);
740 }
741
742 /* Remove task by fd */
743
744 void silc_schedule_task_del_by_fd(SilcSchedule schedule, uint32 fd)
745 {
746   silc_task_del_by_fd(schedule->timeout_queue, fd);
747   silc_task_del_by_fd(schedule->fd_queue, fd);
748   silc_task_del_by_fd(schedule->generic_queue, fd);
749 }
750
751 /* Remove task by task callback. */
752
753 void silc_schedule_task_del_by_callback(SilcSchedule schedule,
754                                         SilcTaskCallback callback)
755 {
756   silc_task_del_by_callback(schedule->timeout_queue, callback);
757   silc_task_del_by_callback(schedule->fd_queue, callback);
758   silc_task_del_by_callback(schedule->generic_queue, callback);
759 }
760
761 /* Remove task by context. */
762
763 void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
764 {
765   silc_task_del_by_context(schedule->timeout_queue, context);
766   silc_task_del_by_context(schedule->fd_queue, context);
767   silc_task_del_by_context(schedule->generic_queue, context);
768 }
769
770 /* Sets a file descriptor to be listened by select() in scheduler. One can
771    call this directly if wanted. This can be called multiple times for
772    one file descriptor to set different iomasks. */
773
774 void silc_schedule_set_listen_fd(SilcSchedule schedule,
775                                  uint32 fd, SilcTaskEvent iomask)
776 {
777   int i;
778   bool found = FALSE;
779
780   silc_mutex_lock(schedule->lock);
781
782   for (i = 0; i < schedule->max_fd; i++)
783     if (schedule->fd_list[i].fd == fd) {
784       schedule->fd_list[i].fd = fd;
785       schedule->fd_list[i].events = iomask;
786       if (i > schedule->last_fd)
787         schedule->last_fd = i;
788       found = TRUE;
789       break;
790     }
791
792   if (!found)
793     for (i = 0; i < schedule->max_fd; i++)
794       if (schedule->fd_list[i].events == 0) {
795         schedule->fd_list[i].fd = fd;
796         schedule->fd_list[i].events = iomask;
797         if (i > schedule->last_fd)
798           schedule->last_fd = i;
799         break;
800       }
801
802   silc_mutex_unlock(schedule->lock);
803 }
804
805 /* Removes a file descriptor from listen list. */
806
807 void silc_schedule_unset_listen_fd(SilcSchedule schedule, uint32 fd)
808 {
809   int i;
810
811   silc_mutex_lock(schedule->lock);
812
813   for (i = 0; i < schedule->max_fd; i++)
814     if (schedule->fd_list[i].fd == fd) {
815       schedule->fd_list[i].fd = 0;
816       schedule->fd_list[i].events = 0;
817       if (schedule->last_fd == i)
818         schedule->last_fd = schedule->max_fd - 1;
819       break;
820     }
821
822   silc_mutex_unlock(schedule->lock);
823 }
824
825 /* Allocates a newtask task queue into the scheduler */
826
827 static void silc_task_queue_alloc(SilcTaskQueue *queue)
828 {
829   *queue = silc_calloc(1, sizeof(**queue));
830   silc_mutex_alloc(&(*queue)->lock);
831 }
832
833 /* Free's a task queue. */
834
835 static void silc_task_queue_free(SilcTaskQueue queue)
836 {
837   silc_mutex_free(queue->lock);
838   silc_free(queue);
839 }
840
841 /* Return task by its fd. */
842
843 static SilcTask silc_task_find(SilcTaskQueue queue, uint32 fd)
844 {
845   SilcTask next;
846
847   if (!queue->task)
848     return NULL;
849
850   next = queue->task;
851
852   while (1) {
853     if (next->fd == fd)
854       return next;
855     if (queue->task == next->next)
856       return NULL;
857     next = next->next;
858   }
859
860   return NULL;
861 }
862
863 /* Adds a non-timeout task into the task queue. This function is used
864    by silc_task_register function. Returns a pointer to the registered 
865    task. */
866
867 static SilcTask silc_task_add(SilcTaskQueue queue, SilcTask newtask, 
868                               SilcTaskPriority priority)
869 {
870   SilcTask task, next, prev;
871
872   /* Take the first task in the queue */
873   task = queue->task;
874
875   switch(priority) {
876   case SILC_TASK_PRI_LOW:
877     /* Lowest priority. The task is added at the end of the list. */
878     prev = task->prev;
879     newtask->prev = prev;
880     newtask->next = task;
881     prev->next = newtask;
882     task->prev = newtask;
883     break;
884   case SILC_TASK_PRI_NORMAL:
885     /* Normal priority. The task is added before lower priority tasks
886        but after tasks with higher priority. */
887     prev = task->prev;
888     while(prev != task) {
889       if (prev->priority > SILC_TASK_PRI_LOW)
890         break;
891       prev = prev->prev;
892     }
893     if (prev == task) {
894       /* There are only lower priorities in the list, we will
895          sit before them and become the first task in the queue. */
896       prev = task->prev;
897       newtask->prev = prev;
898       newtask->next = task;
899       task->prev = newtask;
900       prev->next = newtask;
901
902       /* We are now the first task in queue */
903       queue->task = newtask;
904     } else {
905       /* Found a spot from the list, add the task to the list. */
906       next = prev->next;
907       newtask->prev = prev;
908       newtask->next = next;
909       prev->next = newtask;
910       next->prev = newtask;
911     }
912     break;
913   default:
914     silc_free(newtask);
915     return NULL;
916   }
917
918   return newtask;
919 }
920
921 /* Return the timeout task with smallest timeout. */
922
923 static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first)
924 {
925   SilcTask prev, task;
926
927   prev = first->prev;
928
929   if (first == prev)
930     return first;
931
932   task = first;
933   while (1) {
934     if (first == prev)
935       break;
936
937     if (silc_schedule_task_timeout_compare(&prev->timeout, &task->timeout))
938       task = prev;
939
940     prev = prev->prev;
941   }
942
943   return task;
944 }
945
946 /* Adds a timeout task into the task queue. This function is used by
947    silc_task_register function. Returns a pointer to the registered 
948    task. Timeout tasks are sorted by their timeout value in ascending
949    order. The priority matters if there are more than one task with
950    same timeout. */
951
952 static SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask newtask,
953                                       SilcTaskPriority priority)
954 {
955   SilcTask task, prev, next;
956
957   /* Take the first task in the queue */
958   task = queue->task;
959
960   /* Take last task from the list */
961   prev = task->prev;
962     
963   switch(priority) {
964   case SILC_TASK_PRI_LOW:
965     /* Lowest priority. The task is added at the end of the list. */
966     while(prev != task) {
967
968       /* If we have longer timeout than with the task head of us
969          we have found our spot. */
970       if (silc_schedule_task_timeout_compare(&prev->timeout, 
971                                              &newtask->timeout))
972         break;
973
974       /* If we are equal size of timeout we will be after it. */
975       if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
976                                               &prev->timeout))
977         break;
978
979       /* We have shorter timeout, compare to next one. */
980       prev = prev->prev;
981     }
982     /* Found a spot from the list, add the task to the list. */
983     next = prev->next;
984     newtask->prev = prev;
985     newtask->next = next;
986     prev->next = newtask;
987     next->prev = newtask;
988     
989     if (prev == task) {
990       /* Check if we are going to be the first task in the queue */
991       if (silc_schedule_task_timeout_compare(&prev->timeout, 
992                                              &newtask->timeout))
993         break;
994       if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
995                                               &prev->timeout))
996         break;
997
998       /* We are now the first task in queue */
999       queue->task = newtask;
1000     }
1001     break;
1002   case SILC_TASK_PRI_NORMAL:
1003     /* Normal priority. The task is added before lower priority tasks
1004        but after tasks with higher priority. */
1005     while(prev != task) {
1006
1007       /* If we have longer timeout than with the task head of us
1008          we have found our spot. */
1009       if (silc_schedule_task_timeout_compare(&prev->timeout, 
1010                                              &newtask->timeout))
1011         break;
1012
1013       /* If we are equal size of timeout, priority kicks in place. */
1014       if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
1015                                               &prev->timeout))
1016         if (prev->priority >= SILC_TASK_PRI_NORMAL)
1017           break;
1018
1019       /* We have shorter timeout or higher priority, compare to next one. */
1020       prev = prev->prev;
1021     }
1022     /* Found a spot from the list, add the task to the list. */
1023     next = prev->next;
1024     newtask->prev = prev;
1025     newtask->next = next;
1026     prev->next = newtask;
1027     next->prev = newtask;
1028     
1029     if (prev == task) {
1030       /* Check if we are going to be the first task in the queue */
1031       if (silc_schedule_task_timeout_compare(&prev->timeout, 
1032                                              &newtask->timeout))
1033         break;
1034       if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
1035                                               &prev->timeout))
1036         if (prev->priority >= SILC_TASK_PRI_NORMAL)
1037           break;
1038
1039       /* We are now the first task in queue */
1040       queue->task = newtask;
1041     }
1042     break;
1043   default:
1044     silc_free(newtask);
1045     return NULL;
1046   }
1047
1048   return newtask;
1049 }
1050
1051 /* Removes (unregisters) a task from particular task queue. This function
1052    is used internally by scheduler. This must be called holding the 
1053    queue->lock. */
1054
1055 static int silc_schedule_task_remove(SilcTaskQueue queue, SilcTask task)
1056 {
1057   SilcTask first, old, next;
1058
1059   if (!queue)
1060     return FALSE;
1061
1062   if (!queue->task) {
1063     return FALSE;
1064   }
1065
1066   first = queue->task;
1067
1068   /* Unregister all tasks in queue */
1069   if (task == SILC_ALL_TASKS) {
1070     SILC_LOG_DEBUG(("Removing all tasks at once"));
1071     next = first;
1072
1073     while(1) {
1074       next = next->next;
1075       silc_free(next->prev);
1076       if (next == first)
1077         break;
1078     }
1079
1080     queue->task = NULL;
1081     return TRUE;
1082   }
1083
1084   SILC_LOG_DEBUG(("Removing task"));
1085
1086   /* Unregister the task */
1087   old = first;
1088   while(1) {
1089     if (old == task) {
1090       SilcTask prev, next;
1091
1092       prev = old->prev;
1093       next = old->next;
1094       prev->next = next;
1095       next->prev = prev;
1096
1097       if (prev == old && next == old)
1098         queue->task = NULL;
1099       if (queue->task == old)
1100         queue->task = silc_task_get_first(queue, next);
1101       
1102       silc_free(old);
1103       return TRUE;
1104     }
1105     old = old->prev;
1106
1107     if (old == first) {
1108       return FALSE;
1109     }
1110   }
1111 }
1112
1113 /* Compare two time values. If the first argument is smaller than the
1114    second this function returns TRUE. */
1115
1116 static int silc_schedule_task_timeout_compare(struct timeval *smaller, 
1117                                               struct timeval *bigger)
1118 {
1119   if ((smaller->tv_sec < bigger->tv_sec) ||
1120       ((smaller->tv_sec == bigger->tv_sec) &&
1121        (smaller->tv_usec < bigger->tv_usec)))
1122     return TRUE;
1123
1124   return FALSE;
1125 }
1126
1127 static void silc_task_del_by_fd(SilcTaskQueue queue, uint32 fd)
1128 {
1129   SilcTask next;
1130
1131   SILC_LOG_DEBUG(("Unregister task by fd"));
1132
1133   silc_mutex_lock(queue->lock);
1134
1135   if (!queue->task) {
1136     silc_mutex_unlock(queue->lock);
1137     return;
1138   }
1139
1140   next = queue->task;
1141
1142   while(1) {
1143     if (next->fd == fd)
1144       next->valid = FALSE;
1145     if (queue->task == next->next)
1146       break;
1147     next = next->next;
1148   }
1149
1150   silc_mutex_unlock(queue->lock);
1151 }
1152
1153 static void silc_task_del_by_callback(SilcTaskQueue queue,
1154                                       SilcTaskCallback callback)
1155 {
1156   SilcTask next;
1157
1158   SILC_LOG_DEBUG(("Unregister task by callback"));
1159
1160   silc_mutex_lock(queue->lock);
1161
1162   if (!queue->task) {
1163     silc_mutex_unlock(queue->lock);
1164     return;
1165   }
1166
1167   next = queue->task;
1168
1169   while(1) {
1170     if (next->callback == callback)
1171       next->valid = FALSE;
1172     if (queue->task == next->next)
1173       break;
1174     next = next->next;
1175   }
1176
1177   silc_mutex_unlock(queue->lock);
1178 }
1179
1180 static void silc_task_del_by_context(SilcTaskQueue queue, void *context)
1181 {
1182   SilcTask next;
1183
1184   SILC_LOG_DEBUG(("Unregister task by context"));
1185
1186   silc_mutex_lock(queue->lock);
1187
1188   if (!queue->task) {
1189     silc_mutex_unlock(queue->lock);
1190     return;
1191   }
1192
1193   next = queue->task;
1194
1195   while(1) {
1196     if (next->context == context)
1197       next->valid = FALSE;
1198     if (queue->task == next->next)
1199       break;
1200     next = next->next;
1201   }
1202
1203   silc_mutex_unlock(queue->lock);
1204 }