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