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