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