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