Merged from silc_1_0_branch.
[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 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           silc_mutex_unlock(schedule->generic_queue->lock);
419           SILC_SCHEDULE_UNLOCK(schedule);
420           task->callback(schedule, schedule->app_context,
421                          SILC_TASK_WRITE, fd, task->context);
422           SILC_SCHEDULE_LOCK(schedule);
423           silc_mutex_lock(schedule->generic_queue->lock);
424         }
425
426         if (!task->valid) {
427           /* Invalid (unregistered) tasks are removed from the
428              task queue. */
429           if (schedule->generic_queue->task == task->next) {
430             silc_schedule_task_remove(schedule->generic_queue, task);
431             silc_mutex_unlock(schedule->generic_queue->lock);
432             break;
433           }
434
435           task = task->next;
436           silc_schedule_task_remove(schedule->generic_queue, task);
437           continue;
438         }
439
440         /* Break if there isn't more tasks in the queue */
441         if (schedule->generic_queue->task == task->next)
442           break;
443
444         task = task->next;
445       }                 
446
447       silc_mutex_unlock(schedule->generic_queue->lock);
448     }
449   }
450 }
451
452 /* Executes all tasks whose timeout has expired. The task is removed from
453    the task queue after the callback function has returned. Also, invalid
454    tasks are removed here. We don't have to care about priorities because 
455    tasks are already sorted in their priority order at the registration 
456    phase. */
457 /* This holds the schedule->lock and the schedule->timeout_queue->lock */
458
459 static void silc_schedule_dispatch_timeout(SilcSchedule schedule,
460                                            bool dispatch_all)
461 {
462   SilcTaskQueue queue = schedule->timeout_queue;
463   SilcTask task;
464   struct timeval curtime;
465
466   SILC_LOG_DEBUG(("Running timeout tasks"));
467
468   silc_gettimeofday(&curtime);
469
470   queue = schedule->timeout_queue;
471   if (queue && queue->task) {
472     task = queue->task;
473
474     /* Walk thorugh all tasks in the particular task queue and run all 
475        the expired tasks. */
476     while(1) {
477       /* Execute the task if the timeout has expired */
478       if (dispatch_all ||
479           silc_compare_timeval(&task->timeout, &curtime)) {
480         if (task->valid) {
481           silc_mutex_unlock(queue->lock);
482           SILC_SCHEDULE_UNLOCK(schedule);
483           task->callback(schedule, schedule->app_context,
484                          SILC_TASK_EXPIRE, task->fd, task->context);
485           SILC_SCHEDULE_LOCK(schedule);
486           silc_mutex_lock(queue->lock);
487         }
488
489         /* Break if there isn't more tasks in the queue */
490         if (queue->task == task->next) {
491           silc_schedule_task_remove(queue, task);
492           break;
493         }
494
495         task = task->next;
496
497         /* Remove the task from queue */
498         silc_schedule_task_remove(queue, task->prev);
499       } else {
500         /* The timeout hasn't expired, check for next one */
501
502         /* Break if there isn't more tasks in the queue */
503         if (queue->task == task->next)
504           break;
505
506         task = task->next;
507       }
508     }
509   }
510 }
511
512 /* Calculates next timeout for select(). This is the timeout value
513    when at earliest some of the timeout tasks expire. If this is in the
514    past, they will be run now. */
515 /* This holds the schedule->lock and the schedule->timeout_queue->lock */
516
517 static void silc_schedule_select_timeout(SilcSchedule schedule)
518 {
519   SilcTaskQueue queue = schedule->timeout_queue;
520   SilcTask task;
521   struct timeval curtime;
522
523   /* Get the current time */
524   silc_gettimeofday(&curtime);
525   schedule->timeout = NULL;
526
527   /* First task in the task queue has always the smallest timeout. */
528   task = queue->task;
529   while(1) {
530     if (task && task->valid == TRUE) {
531       /* If the timeout is in past, we will run the task and all other
532          timeout tasks from the past. */
533       if (silc_compare_timeval(&task->timeout, &curtime)) {
534         silc_schedule_dispatch_timeout(schedule, FALSE);
535
536         /* The task(s) has expired and doesn't exist on the task queue
537            anymore. We continue with new timeout. */
538         queue = schedule->timeout_queue;
539         task = queue->task;
540         if (task == NULL || task->valid == FALSE)
541           break;
542       }
543
544       /* Calculate the next timeout for select() */
545       queue->timeout.tv_sec = task->timeout.tv_sec - curtime.tv_sec;
546       queue->timeout.tv_usec = task->timeout.tv_usec - curtime.tv_usec;
547       if (queue->timeout.tv_sec < 0)
548         queue->timeout.tv_sec = 0;
549
550       /* We wouldn't want to go under zero, check for it. */
551       if (queue->timeout.tv_usec < 0) {
552         queue->timeout.tv_sec -= 1;
553         if (queue->timeout.tv_sec < 0)
554           queue->timeout.tv_sec = 0;
555         queue->timeout.tv_usec += 1000000L;
556       }
557
558       /* We've got the timeout value */
559       break;
560     } else {
561       /* Task is not valid, remove it and try next one. */
562       silc_schedule_task_remove(queue, task);
563       task = queue->task;
564       if (queue->task == NULL)
565         break;
566     }
567   }
568
569   /* Save the timeout */
570   if (task) {
571     schedule->timeout = &queue->timeout;
572     SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout->tv_sec,
573                     schedule->timeout->tv_usec));
574   }
575 }
576
577 /* Runs the scheduler once and then returns. */
578
579 bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
580 {
581   struct timeval timeout;
582   int ret;
583
584   SILC_LOG_DEBUG(("In scheduler loop"));
585
586   if (!schedule->is_locked)
587     SILC_SCHEDULE_LOCK(schedule);
588
589   /* Deliver signals if any has been set to be called */
590   if (schedule->signal_tasks) {
591     SILC_SCHEDULE_UNLOCK(schedule);
592     silc_schedule_internal_signals_call(schedule->internal, schedule);
593     schedule->signal_tasks = FALSE;
594     SILC_SCHEDULE_LOCK(schedule);
595   }
596
597   /* If the task queues aren't initialized or we aren't valid anymore
598      we will return */
599   if ((!schedule->fd_queue && !schedule->timeout_queue 
600        && !schedule->generic_queue) || schedule->valid == FALSE) {
601     SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
602     if (!schedule->is_locked)
603       SILC_SCHEDULE_UNLOCK(schedule);
604     return FALSE;
605   }
606
607   /* Calculate next timeout for silc_select(). This is the timeout value
608      when at earliest some of the timeout tasks expire. */
609   silc_mutex_lock(schedule->timeout_queue->lock);
610   silc_schedule_select_timeout(schedule);
611   silc_mutex_unlock(schedule->timeout_queue->lock);
612
613   if (timeout_usecs >= 0) {
614     timeout.tv_sec = 0;
615     timeout.tv_usec = timeout_usecs;
616     schedule->timeout = &timeout;
617   }
618
619   SILC_SCHEDULE_UNLOCK(schedule);
620
621   /* This is the main select(). The program blocks here until some
622      of the selected file descriptors change status or the selected
623      timeout expires. */
624   SILC_LOG_DEBUG(("Select"));
625   ret = silc_select(schedule->fd_list, schedule->last_fd + 1, 
626                     schedule->timeout);
627
628   SILC_SCHEDULE_LOCK(schedule);
629
630   switch (ret) {
631   case -1:
632     /* Error */
633     if (errno == EINTR)
634       break;
635     SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
636     break;
637   case 0:
638     /* Timeout */
639     silc_mutex_lock(schedule->timeout_queue->lock);
640     silc_schedule_dispatch_timeout(schedule, FALSE);
641     silc_mutex_unlock(schedule->timeout_queue->lock);
642     break;
643   default:
644     /* There is some data available now */
645     SILC_LOG_DEBUG(("Running non-timeout tasks"));
646     silc_schedule_dispatch_nontimeout(schedule);
647     break;
648   }
649
650   if (!schedule->is_locked)
651     SILC_SCHEDULE_UNLOCK(schedule);
652
653   return TRUE;
654 }
655
656 /* The SILC scheduler. This is actually the main routine in SILC programs.
657    When this returns the program is to be ended. Before this function can
658    be called, one must call silc_schedule_init function. */
659
660 void silc_schedule(SilcSchedule schedule)
661 {
662   SILC_LOG_DEBUG(("Running scheduler"));
663
664   if (schedule->valid == FALSE) {
665     SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
666     return;
667   }
668
669   SILC_SCHEDULE_LOCK(schedule);
670   schedule->is_locked = TRUE;
671
672   /* Start the scheduler loop */
673   while (silc_schedule_one(schedule, -1)) 
674     ;
675
676   SILC_SCHEDULE_UNLOCK(schedule);
677 }
678
679 /* Wakes up the scheduler. This is used only in multi-threaded
680    environments where threads may add new tasks or remove old tasks
681    from task queues. This is called to wake up the scheduler in the
682    main thread so that it detects the changes in the task queues.
683    If threads support is not compiled in this function has no effect.
684    Implementation of this function is platform specific. */
685
686 void silc_schedule_wakeup(SilcSchedule schedule)
687 {
688 #ifdef SILC_THREADS
689   SILC_LOG_DEBUG(("Wakeup scheduler"));
690   SILC_SCHEDULE_LOCK(schedule);
691   silc_schedule_internal_wakeup(schedule->internal);
692   SILC_SCHEDULE_UNLOCK(schedule);
693 #endif
694 }
695
696 /* Returns the application specific context that was saved into the
697    scheduler in silc_schedule_init function.  The context is also
698    returned to application in task callback functions, but this function
699    may be used to get it as well if needed. */
700
701 void *silc_schedule_get_context(SilcSchedule schedule)
702 {
703   return schedule->app_context;
704 }
705
706 /* Add new task to the scheduler */
707
708 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
709                                 SilcTaskCallback callback, void *context, 
710                                 long seconds, long useconds, 
711                                 SilcTaskType type, 
712                                 SilcTaskPriority priority)
713 {
714   SilcTask newtask;
715   SilcTaskQueue queue;
716   int timeout = FALSE;
717
718   if (!schedule->valid)
719     return NULL;
720
721   SILC_LOG_DEBUG(("Registering new task, fd=%d type=%d priority=%d", fd, 
722                   type, priority));
723
724   queue = SILC_SCHEDULE_GET_QUEUE(type);
725     
726   /* If the task is generic task, we check whether this task has already
727      been registered. Generic tasks are registered only once and after that
728      the same task applies to all file descriptors to be registered. */
729   if (type == SILC_TASK_GENERIC) {
730     silc_mutex_lock(queue->lock);
731
732     if (queue->task) {
733       SilcTask task = queue->task;
734       while(1) {
735         if ((task->callback == callback) && (task->context == context)) {
736           SILC_LOG_DEBUG(("Found matching generic task, using the match"));
737           
738           silc_mutex_unlock(queue->lock);
739
740           /* Add the fd to be listened, the task found now applies to this
741              fd as well. */
742           silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ, FALSE);
743           return task;
744         }
745         
746         if (queue->task == task->next)
747           break;
748         
749         task = task->next;
750       }
751     }
752
753     silc_mutex_unlock(queue->lock);
754   }
755
756   newtask = silc_calloc(1, sizeof(*newtask));
757   newtask->fd = fd;
758   newtask->context = context;
759   newtask->callback = callback;
760   newtask->valid = TRUE;
761   newtask->priority = priority;
762   newtask->type = type;
763   newtask->next = newtask;
764   newtask->prev = newtask;
765
766   /* Create timeout if marked to be timeout task */
767   if (((seconds + useconds) > 0) && (type == SILC_TASK_TIMEOUT)) {
768     silc_gettimeofday(&newtask->timeout);
769     newtask->timeout.tv_sec += seconds + (useconds / 1000000L);
770     newtask->timeout.tv_usec += (useconds % 1000000L);
771     if (newtask->timeout.tv_usec > 999999L) {
772       newtask->timeout.tv_sec += 1;
773       newtask->timeout.tv_usec -= 1000000L;
774     }
775     timeout = TRUE;
776   }
777
778   /* If the task is non-timeout task we have to tell the scheduler that we
779      would like to have these tasks scheduled at some odd distant future. */
780   if (type != SILC_TASK_TIMEOUT)
781     silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ, FALSE);
782
783   silc_mutex_lock(queue->lock);
784
785   /* Is this first task of the queue? */
786   if (queue->task == NULL) {
787     queue->task = newtask;
788     silc_mutex_unlock(queue->lock);
789     return newtask;
790   }
791
792   if (timeout)
793     newtask = silc_task_add_timeout(queue, newtask, priority);
794   else
795     newtask = silc_task_add(queue, newtask, priority);
796
797   silc_mutex_unlock(queue->lock);
798
799   return newtask;
800 }
801
802 /* Removes a task from the scheduler */
803
804 void silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
805 {
806   SilcTaskQueue queue = SILC_SCHEDULE_GET_QUEUE(task->type);
807
808   /* Unregister all tasks */
809   if (task == SILC_ALL_TASKS) {
810     SilcTask next;
811     SILC_LOG_DEBUG(("Unregistering all tasks at once"));
812
813     silc_mutex_lock(queue->lock);
814
815     if (!queue->task) {
816       silc_mutex_unlock(queue->lock);
817       return;
818     }
819
820     next = queue->task;
821     
822     while(1) {
823       if (next->valid)
824         next->valid = FALSE;
825       if (queue->task == next->next)
826         break;
827       next = next->next;
828     }
829
830     silc_mutex_unlock(queue->lock);
831     return;
832   }
833
834   SILC_LOG_DEBUG(("Unregistering task"));
835
836   silc_mutex_lock(queue->lock);
837
838   /* Unregister the specific task */
839   if (task->valid)
840     task->valid = FALSE;
841
842   silc_mutex_unlock(queue->lock);
843 }
844
845 /* Remove task by fd */
846
847 void silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
848 {
849   SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
850
851   silc_task_del_by_fd(schedule->timeout_queue, fd);
852   silc_task_del_by_fd(schedule->fd_queue, fd);
853 }
854
855 /* Remove task by task callback. */
856
857 void silc_schedule_task_del_by_callback(SilcSchedule schedule,
858                                         SilcTaskCallback callback)
859 {
860   SILC_LOG_DEBUG(("Unregister task by callback"));
861
862   silc_task_del_by_callback(schedule->timeout_queue, callback);
863   silc_task_del_by_callback(schedule->fd_queue, callback);
864   silc_task_del_by_callback(schedule->generic_queue, callback);
865 }
866
867 /* Remove task by context. */
868
869 void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
870 {
871   SILC_LOG_DEBUG(("Unregister task by context"));
872
873   silc_task_del_by_context(schedule->timeout_queue, context);
874   silc_task_del_by_context(schedule->fd_queue, context);
875   silc_task_del_by_context(schedule->generic_queue, context);
876 }
877
878 /* Sets a file descriptor to be listened by select() in scheduler. One can
879    call this directly if wanted. This can be called multiple times for
880    one file descriptor to set different iomasks. */
881
882 void silc_schedule_set_listen_fd(SilcSchedule schedule, SilcUInt32 fd,
883                                  SilcTaskEvent mask, bool send_events)
884 {
885   int i;
886   bool found = FALSE;
887
888   if (!schedule->valid)
889     return;
890
891   SILC_SCHEDULE_LOCK(schedule);
892
893   for (i = 0; i < schedule->max_fd; i++)
894     if (schedule->fd_list[i].fd == fd) {
895       schedule->fd_list[i].fd = fd;
896       schedule->fd_list[i].events = mask;
897       if (i > schedule->last_fd)
898         schedule->last_fd = i;
899       found = TRUE;
900       if (send_events) {
901         schedule->fd_list[i].revents = mask;
902         silc_schedule_dispatch_nontimeout(schedule);
903       }
904       break;
905     }
906
907   if (!found)
908     for (i = 0; i < schedule->max_fd; i++)
909       if (schedule->fd_list[i].events == 0) {
910         schedule->fd_list[i].fd = fd;
911         schedule->fd_list[i].events = mask;
912         if (i > schedule->last_fd)
913           schedule->last_fd = i;
914         if (send_events) {
915           schedule->fd_list[i].revents = mask;
916           silc_schedule_dispatch_nontimeout(schedule);
917         }
918         break;
919       }
920
921   SILC_SCHEDULE_UNLOCK(schedule);
922 }
923
924 /* Removes a file descriptor from listen list. */
925
926 void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
927 {
928   int i;
929
930   SILC_SCHEDULE_LOCK(schedule);
931
932   SILC_LOG_DEBUG(("Unset listen fd %d", fd));
933
934   for (i = 0; i < schedule->max_fd; i++)
935     if (schedule->fd_list[i].fd == fd) {
936       schedule->fd_list[i].fd = 0;
937       schedule->fd_list[i].events = 0;
938       if (schedule->last_fd == i)
939         schedule->last_fd = schedule->max_fd - 1;
940       break;
941     }
942
943   SILC_SCHEDULE_UNLOCK(schedule);
944 }
945
946 /* Register a new signal */
947
948 void silc_schedule_signal_register(SilcSchedule schedule, SilcUInt32 signal,
949                                    SilcTaskCallback callback, void *context)
950 {
951   silc_schedule_internal_signal_register(schedule->internal, signal,
952                                          callback, context);
953 }
954
955 /* Unregister a new signal */
956
957 void silc_schedule_signal_unregister(SilcSchedule schedule, SilcUInt32 signal,
958                                      SilcTaskCallback callback, void *context)
959 {
960   silc_schedule_internal_signal_unregister(schedule->internal, signal,
961                                            callback, context);
962 }
963
964 /* Call signal indicated by `signal'. */
965
966 void silc_schedule_signal_call(SilcSchedule schedule, SilcUInt32 signal)
967 {
968   /* Mark that signals needs to be delivered later. */
969   silc_schedule_internal_signal_call(schedule->internal, signal);
970   schedule->signal_tasks = TRUE;
971 }
972
973 /* Allocates a newtask task queue into the scheduler */
974
975 static void silc_task_queue_alloc(SilcTaskQueue *queue)
976 {
977   *queue = silc_calloc(1, sizeof(**queue));
978   silc_mutex_alloc(&(*queue)->lock);
979 }
980
981 /* Free's a task queue. */
982
983 static void silc_task_queue_free(SilcTaskQueue queue)
984 {
985   silc_mutex_free(queue->lock);
986   memset(queue, 'F', sizeof(*queue));
987   silc_free(queue);
988 }
989
990 /* Return task by its fd. */
991
992 static SilcTask silc_task_find(SilcTaskQueue queue, SilcUInt32 fd)
993 {
994   SilcTask next;
995
996   if (!queue->task)
997     return NULL;
998
999   next = queue->task;
1000
1001   while (1) {
1002     if (next->fd == fd)
1003       return next;
1004     if (queue->task == next->next)
1005       return NULL;
1006     next = next->next;
1007   }
1008
1009   return NULL;
1010 }
1011
1012 /* Adds a non-timeout task into the task queue. This function is used
1013    by silc_task_register function. Returns a pointer to the registered 
1014    task. */
1015
1016 static SilcTask silc_task_add(SilcTaskQueue queue, SilcTask newtask, 
1017                               SilcTaskPriority priority)
1018 {
1019   SilcTask task, next, prev;
1020
1021   /* Take the first task in the queue */
1022   task = queue->task;
1023
1024   switch(priority) {
1025   case SILC_TASK_PRI_LOW:
1026     /* Lowest priority. The task is added at the end of the list. */
1027     prev = task->prev;
1028     newtask->prev = prev;
1029     newtask->next = task;
1030     prev->next = newtask;
1031     task->prev = newtask;
1032     break;
1033   case SILC_TASK_PRI_NORMAL:
1034     /* Normal priority. The task is added before lower priority tasks
1035        but after tasks with higher priority. */
1036     prev = task->prev;
1037     while(prev != task) {
1038       if (prev->priority > SILC_TASK_PRI_LOW)
1039         break;
1040       prev = prev->prev;
1041     }
1042     if (prev == task) {
1043       /* There are only lower priorities in the list, we will
1044          sit before them and become the first task in the queue. */
1045       prev = task->prev;
1046       newtask->prev = prev;
1047       newtask->next = task;
1048       task->prev = newtask;
1049       prev->next = newtask;
1050
1051       /* We are now the first task in queue */
1052       queue->task = newtask;
1053     } else {
1054       /* Found a spot from the list, add the task to the list. */
1055       next = prev->next;
1056       newtask->prev = prev;
1057       newtask->next = next;
1058       prev->next = newtask;
1059       next->prev = newtask;
1060     }
1061     break;
1062   default:
1063     silc_free(newtask);
1064     return NULL;
1065   }
1066
1067   return newtask;
1068 }
1069
1070 /* Return the timeout task with smallest timeout. */
1071
1072 static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first)
1073 {
1074   SilcTask prev, task;
1075
1076   prev = first->prev;
1077
1078   if (first == prev)
1079     return first;
1080
1081   task = first;
1082   while (1) {
1083     if (first == prev)
1084       break;
1085
1086     if (silc_compare_timeval(&prev->timeout, &task->timeout))
1087       task = prev;
1088
1089     prev = prev->prev;
1090   }
1091
1092   return task;
1093 }
1094
1095 /* Adds a timeout task into the task queue. This function is used by
1096    silc_task_register function. Returns a pointer to the registered 
1097    task. Timeout tasks are sorted by their timeout value in ascending
1098    order. The priority matters if there are more than one task with
1099    same timeout. */
1100
1101 static SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask newtask,
1102                                       SilcTaskPriority priority)
1103 {
1104   SilcTask task, prev, next;
1105
1106   /* Take the first task in the queue */
1107   task = queue->task;
1108
1109   /* Take last task from the list */
1110   prev = task->prev;
1111     
1112   switch(priority) {
1113   case SILC_TASK_PRI_LOW:
1114     /* Lowest priority. The task is added at the end of the list. */
1115     while(prev != task) {
1116
1117       /* If we have longer timeout than with the task head of us
1118          we have found our spot. */
1119       if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
1120         break;
1121
1122       /* If we are equal size of timeout we will be after it. */
1123       if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
1124         break;
1125
1126       /* We have shorter timeout, compare to next one. */
1127       prev = prev->prev;
1128     }
1129     /* Found a spot from the list, add the task to the list. */
1130     next = prev->next;
1131     newtask->prev = prev;
1132     newtask->next = next;
1133     prev->next = newtask;
1134     next->prev = newtask;
1135     
1136     if (prev == task) {
1137       /* Check if we are going to be the first task in the queue */
1138       if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
1139         break;
1140       if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
1141         break;
1142
1143       /* We are now the first task in queue */
1144       queue->task = newtask;
1145     }
1146     break;
1147   case SILC_TASK_PRI_NORMAL:
1148     /* Normal priority. The task is added before lower priority tasks
1149        but after tasks with higher priority. */
1150     while(prev != task) {
1151
1152       /* If we have longer timeout than with the task head of us
1153          we have found our spot. */
1154       if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
1155         break;
1156
1157       /* If we are equal size of timeout, priority kicks in place. */
1158       if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
1159         if (prev->priority >= SILC_TASK_PRI_NORMAL)
1160           break;
1161
1162       /* We have shorter timeout or higher priority, compare to next one. */
1163       prev = prev->prev;
1164     }
1165     /* Found a spot from the list, add the task to the list. */
1166     next = prev->next;
1167     newtask->prev = prev;
1168     newtask->next = next;
1169     prev->next = newtask;
1170     next->prev = newtask;
1171     
1172     if (prev == task) {
1173       /* Check if we are going to be the first task in the queue */
1174       if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
1175         break;
1176       if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
1177         if (prev->priority >= SILC_TASK_PRI_NORMAL)
1178           break;
1179
1180       /* We are now the first task in queue */
1181       queue->task = newtask;
1182     }
1183     break;
1184   default:
1185     silc_free(newtask);
1186     return NULL;
1187   }
1188
1189   return newtask;
1190 }
1191
1192 /* Removes (unregisters) a task from particular task queue. This function
1193    is used internally by scheduler. This must be called holding the 
1194    queue->lock. */
1195
1196 static int silc_schedule_task_remove(SilcTaskQueue queue, SilcTask task)
1197 {
1198   SilcTask first, old, next;
1199
1200   if (!queue || !task)
1201     return FALSE;
1202
1203   if (!queue->task) {
1204     return FALSE;
1205   }
1206
1207   first = queue->task;
1208
1209   /* Unregister all tasks in queue */
1210   if (task == SILC_ALL_TASKS) {
1211     SILC_LOG_DEBUG(("Removing all tasks at once"));
1212     next = first;
1213
1214     while(1) {
1215       old = next->next;
1216       silc_free(next);
1217       if (old == first)
1218         break;
1219       next = old;
1220     }
1221
1222     queue->task = NULL;
1223     return TRUE;
1224   }
1225
1226   SILC_LOG_DEBUG(("Removing task"));
1227
1228   /* Unregister the task */
1229   old = first;
1230   while(1) {
1231     if (old == task) {
1232       SilcTask prev, next;
1233
1234       prev = old->prev;
1235       next = old->next;
1236       prev->next = next;
1237       next->prev = prev;
1238
1239       if (prev == old && next == old)
1240         queue->task = NULL;
1241       if (queue->task == old)
1242         queue->task = silc_task_get_first(queue, next);
1243       
1244       silc_free(old);
1245       return TRUE;
1246     }
1247     old = old->prev;
1248
1249     if (old == first) {
1250       return FALSE;
1251     }
1252   }
1253 }
1254
1255 static void silc_task_del_by_fd(SilcTaskQueue queue, SilcUInt32 fd)
1256 {
1257   SilcTask next;
1258
1259   silc_mutex_lock(queue->lock);
1260
1261   if (!queue->task) {
1262     silc_mutex_unlock(queue->lock);
1263     return;
1264   }
1265
1266   next = queue->task;
1267
1268   while(1) {
1269     if (next->fd == fd)
1270       next->valid = FALSE;
1271     if (queue->task == next->next)
1272       break;
1273     next = next->next;
1274   }
1275
1276   silc_mutex_unlock(queue->lock);
1277 }
1278
1279 static void silc_task_del_by_callback(SilcTaskQueue queue,
1280                                       SilcTaskCallback callback)
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->callback == callback)
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 }
1303
1304 static void silc_task_del_by_context(SilcTaskQueue queue, void *context)
1305 {
1306   SilcTask next;
1307
1308   silc_mutex_lock(queue->lock);
1309
1310   if (!queue->task) {
1311     silc_mutex_unlock(queue->lock);
1312     return;
1313   }
1314
1315   next = queue->task;
1316
1317   while(1) {
1318     if (next->context == context)
1319       next->valid = FALSE;
1320     if (queue->task == next->next)
1321       break;
1322     next = next->next;
1323   }
1324
1325   silc_mutex_unlock(queue->lock);
1326 }