The scheduler dispatches pending timeouts and signals now
[crypto.git] / lib / silcutil / silcschedule.c
1 /*
2
3   silcschedule.c
4
5   Author: Pekka Riikonen <priikone@silcnet.org>
6
7   Copyright (C) 1998 - 2001 Pekka Riikonen
8
9   This program is free software; you can redistribute it and/or modify
10   it under the terms of the GNU General Public License as published by
11   the Free Software Foundation; either version 2 of the License, or
12   (at your option) any later version.
13   
14   This program is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   GNU General Public License for more details.
18
19 */
20 /* $Id$ */
21
22 #include "silcincludes.h"
23 #include "silcschedule_i.h"
24
25 /* Forward declarations */
26 typedef struct SilcTaskQueueStruct *SilcTaskQueue;
27
28 /* System specific routines. Implemented under unix/, win32/ and such. */
29
30 /* System specific select(). Returns same values as normal select(). */
31 int silc_select(SilcScheduleFd fds, SilcUInt32 fds_count, 
32                 struct timeval *timeout);
33
34 /* Initializes the platform specific scheduler.  This for example initializes
35    the wakeup mechanism of the scheduler.  In multi-threaded environment
36    the scheduler needs to be wakenup when tasks are added or removed from
37    the task queues.  Returns context to the platform specific scheduler. */
38 void *silc_schedule_internal_init(SilcSchedule schedule);
39
40 /* Uninitializes the platform specific scheduler context. */
41 void silc_schedule_internal_uninit(void *context);
42
43 /* Wakes up the scheduler. This is platform specific routine */
44 void silc_schedule_internal_wakeup(void *context);
45
46 /* Register signal */
47 void silc_schedule_internal_signal_register(void *context,
48                                             SilcUInt32 signal,
49                                             SilcTaskCallback callback,
50                                             void *callback_context);
51
52 /* Unregister signal */
53 void silc_schedule_internal_signal_unregister(void *context,
54                                               SilcUInt32 signal,
55                                               SilcTaskCallback callback,
56                                               void *callback_context);
57
58 /* Mark signal to be called later. */
59 void silc_schedule_internal_signal_call(void *context, SilcUInt32 signal);
60
61 /* Call all signals */
62 void silc_schedule_internal_signals_call(void *context,
63                                          SilcSchedule schedule);
64
65 /* Block registered signals in scheduler. */
66 void silc_schedule_internal_signals_block(void *context);
67
68 /* Unblock registered signals in schedule. */
69 void silc_schedule_internal_signals_unblock(void *context);
70
71 /* Internal task management routines. */
72
73 static void silc_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   SilcTaskQueue fd_queue;
214   SilcTaskQueue timeout_queue;
215   SilcTaskQueue generic_queue;
216   SilcScheduleFd fd_list;
217   SilcUInt32 max_fd;
218   SilcUInt32 last_fd;
219   struct timeval *timeout;
220   bool valid;
221   void *internal;
222   SILC_MUTEX_DEFINE(lock);
223   bool is_locked;
224   bool signal_tasks;
225 };
226
227 /* Initializes the scheduler. This returns the scheduler context that
228    is given as arugment usually to all silc_schedule_* functions.
229    The `max_tasks' indicates the number of maximum tasks that the
230    scheduler can handle. */
231
232 SilcSchedule silc_schedule_init(int max_tasks)
233 {
234   SilcSchedule schedule;
235
236   SILC_LOG_DEBUG(("Initializing scheduler"));
237
238   schedule = silc_calloc(1, sizeof(*schedule));
239
240   /* Allocate three task queues, one for file descriptor based tasks,
241      one for timeout tasks and one for generic tasks. */
242   silc_task_queue_alloc(&schedule->fd_queue);
243   silc_task_queue_alloc(&schedule->timeout_queue);
244   silc_task_queue_alloc(&schedule->generic_queue);
245
246   if (!max_tasks)
247     max_tasks = 200;
248
249   /* Initialize the scheduler */
250   schedule->fd_list = silc_calloc(max_tasks, sizeof(*schedule->fd_list));
251   schedule->max_fd = max_tasks;
252   schedule->timeout = NULL;
253   schedule->valid = TRUE;
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);
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_mutex_lock(schedule->timeout_queue->lock);
278   silc_schedule_dispatch_timeout(schedule, TRUE);
279   silc_mutex_unlock(schedule->timeout_queue->lock);
280
281   /* Deliver signals before going away */
282   if (schedule->signal_tasks) {
283     SILC_SCHEDULE_UNLOCK(schedule);
284     silc_schedule_internal_signals_call(schedule->internal, schedule);
285     schedule->signal_tasks = FALSE;
286     SILC_SCHEDULE_LOCK(schedule);
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, last_fd = schedule->last_fd;
346   SilcUInt32 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, SILC_TASK_READ, task->fd, task->context);
370         SILC_SCHEDULE_LOCK(schedule);
371         silc_mutex_lock(schedule->fd_queue->lock);
372       }
373
374       /* Is the task ready for writing */
375       if (task->valid && schedule->fd_list[i].revents & SILC_TASK_WRITE) {
376         silc_mutex_unlock(schedule->fd_queue->lock);
377         SILC_SCHEDULE_UNLOCK(schedule);
378         task->callback(schedule, SILC_TASK_WRITE, task->fd, task->context);
379         SILC_SCHEDULE_LOCK(schedule);
380         silc_mutex_lock(schedule->fd_queue->lock);
381       }
382
383       if (!task->valid)
384         silc_schedule_task_remove(schedule->fd_queue, task);
385
386       silc_mutex_unlock(schedule->fd_queue->lock);
387     } else {
388       /* Run generic tasks for this fd. */
389
390       silc_mutex_unlock(schedule->fd_queue->lock);
391
392       silc_mutex_lock(schedule->generic_queue->lock);
393       if (!schedule->generic_queue->task) {
394         silc_mutex_unlock(schedule->generic_queue->lock);
395         continue;
396       }
397
398       task = schedule->generic_queue->task;
399       while(1) {
400         /* Validity of the task is checked always before and after
401            execution beacuse the task might have been unregistered
402            in the callback function, ie. it is not valid anymore. */
403
404         /* Is the task ready for reading */                             
405         if (task->valid && schedule->fd_list[i].revents & SILC_TASK_READ) {
406           silc_mutex_unlock(schedule->generic_queue->lock);
407           SILC_SCHEDULE_UNLOCK(schedule);
408           task->callback(schedule, SILC_TASK_READ, fd, task->context);
409           SILC_SCHEDULE_LOCK(schedule);
410           silc_mutex_lock(schedule->generic_queue->lock);
411         }
412
413         /* Is the task ready for writing */                             
414         if (task->valid && schedule->fd_list[i].revents & SILC_TASK_WRITE) {
415           silc_mutex_unlock(schedule->generic_queue->lock);
416           SILC_SCHEDULE_UNLOCK(schedule);
417           task->callback(schedule, SILC_TASK_WRITE, fd, task->context);
418           SILC_SCHEDULE_LOCK(schedule);
419           silc_mutex_lock(schedule->generic_queue->lock);
420         }
421
422         if (!task->valid) {
423           /* Invalid (unregistered) tasks are removed from the
424              task queue. */
425           if (schedule->generic_queue->task == task->next) {
426             silc_schedule_task_remove(schedule->generic_queue, task);
427             silc_mutex_unlock(schedule->generic_queue->lock);
428             break;
429           }
430
431           task = task->next;
432           silc_schedule_task_remove(schedule->generic_queue, task);
433           continue;
434         }
435
436         /* Break if there isn't more tasks in the queue */
437         if (schedule->generic_queue->task == task->next)
438           break;
439
440         task = task->next;
441       }                 
442
443       silc_mutex_unlock(schedule->generic_queue->lock);
444     }
445   }
446 }
447
448 /* Executes all tasks whose timeout has expired. The task is removed from
449    the task queue after the callback function has returned. Also, invalid
450    tasks are removed here. We don't have to care about priorities because 
451    tasks are already sorted in their priority order at the registration 
452    phase. */
453 /* This holds the schedule->lock and the schedule->timeout_queue->lock */
454
455 static void silc_schedule_dispatch_timeout(SilcSchedule schedule,
456                                            bool dispatch_all)
457 {
458   SilcTaskQueue queue = schedule->timeout_queue;
459   SilcTask task;
460   struct timeval curtime;
461
462   SILC_LOG_DEBUG(("Running timeout tasks"));
463
464   silc_gettimeofday(&curtime);
465
466   queue = schedule->timeout_queue;
467   if (queue && queue->task) {
468     task = queue->task;
469
470     /* Walk thorugh all tasks in the particular task queue and run all 
471        the expired tasks. */
472     while(1) {
473       /* Execute the task if the timeout has expired */
474       if (dispatch_all ||
475           silc_schedule_task_timeout_compare(&task->timeout, &curtime)) {
476         if (task->valid) {
477           silc_mutex_unlock(queue->lock);
478           SILC_SCHEDULE_UNLOCK(schedule);
479           task->callback(schedule, SILC_TASK_EXPIRE, task->fd, task->context);
480           SILC_SCHEDULE_LOCK(schedule);
481           silc_mutex_lock(queue->lock);
482         }
483
484         /* Break if there isn't more tasks in the queue */
485         if (queue->task == task->next) {
486           silc_schedule_task_remove(queue, task);
487           break;
488         }
489
490         task = task->next;
491
492         /* Remove the task from queue */
493         silc_schedule_task_remove(queue, task->prev);
494       } else {
495         /* The timeout hasn't expired, check for next one */
496
497         /* Break if there isn't more tasks in the queue */
498         if (queue->task == task->next)
499           break;
500
501         task = task->next;
502       }
503     }
504   }
505 }
506
507 /* Calculates next timeout for select(). This is the timeout value
508    when at earliest some of the timeout tasks expire. If this is in the
509    past, they will be run now. */
510 /* This holds the schedule->lock and the schedule->timeout_queue->lock */
511
512 static void silc_schedule_select_timeout(SilcSchedule schedule)
513 {
514   SilcTaskQueue queue = schedule->timeout_queue;
515   SilcTask task;
516   struct timeval curtime;
517
518   /* Get the current time */
519   silc_gettimeofday(&curtime);
520   schedule->timeout = NULL;
521
522   /* First task in the task queue has always the smallest timeout. */
523   task = queue->task;
524   while(1) {
525     if (task && task->valid == TRUE) {
526       /* If the timeout is in past, we will run the task and all other
527          timeout tasks from the past. */
528       if (silc_schedule_task_timeout_compare(&task->timeout, &curtime)) {
529         silc_schedule_dispatch_timeout(schedule, FALSE);
530
531         /* The task(s) has expired and doesn't exist on the task queue
532            anymore. We continue with new timeout. */
533         queue = schedule->timeout_queue;
534         task = queue->task;
535         if (task == NULL || task->valid == FALSE)
536           break;
537       }
538
539       /* Calculate the next timeout for select() */
540       queue->timeout.tv_sec = task->timeout.tv_sec - curtime.tv_sec;
541       queue->timeout.tv_usec = task->timeout.tv_usec - curtime.tv_usec;
542       if (queue->timeout.tv_sec < 0)
543         queue->timeout.tv_sec = 0;
544
545       /* We wouldn't want to go under zero, check for it. */
546       if (queue->timeout.tv_usec < 0) {
547         queue->timeout.tv_sec -= 1;
548         if (queue->timeout.tv_sec < 0)
549           queue->timeout.tv_sec = 0;
550         queue->timeout.tv_usec += 1000000L;
551       }
552
553       /* We've got the timeout value */
554       break;
555     } else {
556       /* Task is not valid, remove it and try next one. */
557       silc_schedule_task_remove(queue, task);
558       task = queue->task;
559       if (queue->task == NULL)
560         break;
561     }
562   }
563
564   /* Save the timeout */
565   if (task) {
566     schedule->timeout = &queue->timeout;
567     SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout->tv_sec,
568                     schedule->timeout->tv_usec));
569   }
570 }
571
572 /* Runs the scheduler once and then returns. */
573
574 bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
575 {
576   struct timeval timeout;
577   int ret;
578
579   SILC_LOG_DEBUG(("In scheduler loop"));
580
581   if (!schedule->is_locked)
582     SILC_SCHEDULE_LOCK(schedule);
583
584   /* Deliver signals if any has been set to be called */
585   if (schedule->signal_tasks) {
586     SILC_SCHEDULE_UNLOCK(schedule);
587     silc_schedule_internal_signals_call(schedule->internal, schedule);
588     schedule->signal_tasks = FALSE;
589     SILC_SCHEDULE_LOCK(schedule);
590   }
591
592   /* If the task queues aren't initialized or we aren't valid anymore
593      we will return */
594   if ((!schedule->fd_queue && !schedule->timeout_queue 
595        && !schedule->generic_queue) || schedule->valid == FALSE) {
596     SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
597     if (!schedule->is_locked)
598       SILC_SCHEDULE_UNLOCK(schedule);
599     return FALSE;
600   }
601
602   /* Calculate next timeout for silc_select(). This is the timeout value
603      when at earliest some of the timeout tasks expire. */
604   silc_mutex_lock(schedule->timeout_queue->lock);
605   silc_schedule_select_timeout(schedule);
606   silc_mutex_unlock(schedule->timeout_queue->lock);
607
608   if (timeout_usecs >= 0) {
609     timeout.tv_sec = 0;
610     timeout.tv_usec = timeout_usecs;
611     schedule->timeout = &timeout;
612   }
613
614   SILC_SCHEDULE_UNLOCK(schedule);
615
616   /* This is the main select(). The program blocks here until some
617      of the selected file descriptors change status or the selected
618      timeout expires. */
619   SILC_LOG_DEBUG(("Select"));
620   ret = silc_select(schedule->fd_list, schedule->last_fd + 1, 
621                     schedule->timeout);
622
623   SILC_SCHEDULE_LOCK(schedule);
624
625   switch (ret) {
626   case -1:
627     /* Error */
628     if (errno == EINTR)
629       break;
630     SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
631     break;
632   case 0:
633     /* Timeout */
634     silc_mutex_lock(schedule->timeout_queue->lock);
635     silc_schedule_dispatch_timeout(schedule, FALSE);
636     silc_mutex_unlock(schedule->timeout_queue->lock);
637     break;
638   default:
639     /* There is some data available now */
640     SILC_LOG_DEBUG(("Running non-timeout tasks"));
641     silc_schedule_dispatch_nontimeout(schedule);
642     break;
643   }
644
645   if (!schedule->is_locked)
646     SILC_SCHEDULE_UNLOCK(schedule);
647
648   return TRUE;
649 }
650
651 /* The SILC scheduler. This is actually the main routine in SILC programs.
652    When this returns the program is to be ended. Before this function can
653    be called, one must call silc_schedule_init function. */
654
655 void silc_schedule(SilcSchedule schedule)
656 {
657   SILC_LOG_DEBUG(("Running scheduler"));
658
659   if (schedule->valid == FALSE) {
660     SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
661     return;
662   }
663
664   SILC_SCHEDULE_LOCK(schedule);
665   schedule->is_locked = TRUE;
666
667   /* Start the scheduler loop */
668   while (silc_schedule_one(schedule, -1)) 
669     ;
670
671   SILC_SCHEDULE_UNLOCK(schedule);
672 }
673
674 /* Wakes up the scheduler. This is used only in multi-threaded
675    environments where threads may add new tasks or remove old tasks
676    from task queues. This is called to wake up the scheduler in the
677    main thread so that it detects the changes in the task queues.
678    If threads support is not compiled in this function has no effect.
679    Implementation of this function is platform specific. */
680
681 void silc_schedule_wakeup(SilcSchedule schedule)
682 {
683 #ifdef SILC_THREADS
684   SILC_LOG_DEBUG(("Wakeup scheduler"));
685   SILC_SCHEDULE_LOCK(schedule);
686   silc_schedule_internal_wakeup(schedule->internal);
687   SILC_SCHEDULE_UNLOCK(schedule);
688 #endif
689 }
690
691 /* Add new task to the scheduler */
692
693 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
694                                 SilcTaskCallback callback, void *context, 
695                                 long seconds, long useconds, 
696                                 SilcTaskType type, 
697                                 SilcTaskPriority priority)
698 {
699   SilcTask newtask;
700   SilcTaskQueue queue;
701   int timeout = FALSE;
702
703   if (!schedule->valid)
704     return NULL;
705
706   SILC_LOG_DEBUG(("Registering new task, fd=%d type=%d priority=%d", fd, 
707                   type, priority));
708
709   queue = SILC_SCHEDULE_GET_QUEUE(type);
710     
711   /* If the task is generic task, we check whether this task has already
712      been registered. Generic tasks are registered only once and after that
713      the same task applies to all file descriptors to be registered. */
714   if (type == SILC_TASK_GENERIC) {
715     silc_mutex_lock(queue->lock);
716
717     if (queue->task) {
718       SilcTask task = queue->task;
719       while(1) {
720         if ((task->callback == callback) && (task->context == context)) {
721           SILC_LOG_DEBUG(("Found matching generic task, using the match"));
722           
723           silc_mutex_unlock(queue->lock);
724
725           /* Add the fd to be listened, the task found now applies to this
726              fd as well. */
727           silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ);
728           return task;
729         }
730         
731         if (queue->task == task->next)
732           break;
733         
734         task = task->next;
735       }
736     }
737
738     silc_mutex_unlock(queue->lock);
739   }
740
741   newtask = silc_calloc(1, sizeof(*newtask));
742   newtask->fd = fd;
743   newtask->context = context;
744   newtask->callback = callback;
745   newtask->valid = TRUE;
746   newtask->priority = priority;
747   newtask->type = type;
748   newtask->next = newtask;
749   newtask->prev = newtask;
750
751   /* Create timeout if marked to be timeout task */
752   if (((seconds + useconds) > 0) && (type == SILC_TASK_TIMEOUT)) {
753     silc_gettimeofday(&newtask->timeout);
754     newtask->timeout.tv_sec += seconds + (useconds / 1000000L);
755     newtask->timeout.tv_usec += (useconds % 1000000L);
756     if (newtask->timeout.tv_usec > 999999L) {
757       newtask->timeout.tv_sec += 1;
758       newtask->timeout.tv_usec -= 1000000L;
759     }
760     timeout = TRUE;
761   }
762
763   /* If the task is non-timeout task we have to tell the scheduler that we
764      would like to have these tasks scheduled at some odd distant future. */
765   if (type != SILC_TASK_TIMEOUT)
766     silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ);
767
768   silc_mutex_lock(queue->lock);
769
770   /* Is this first task of the queue? */
771   if (queue->task == NULL) {
772     queue->task = newtask;
773     silc_mutex_unlock(queue->lock);
774     return newtask;
775   }
776
777   if (timeout)
778     newtask = silc_task_add_timeout(queue, newtask, priority);
779   else
780     newtask = silc_task_add(queue, newtask, priority);
781
782   silc_mutex_unlock(queue->lock);
783
784   return newtask;
785 }
786
787 /* Removes a task from the scheduler */
788
789 void silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
790 {
791   SilcTaskQueue queue = SILC_SCHEDULE_GET_QUEUE(task->type);
792
793   /* Unregister all tasks */
794   if (task == SILC_ALL_TASKS) {
795     SilcTask next;
796     SILC_LOG_DEBUG(("Unregistering all tasks at once"));
797
798     silc_mutex_lock(queue->lock);
799
800     if (!queue->task) {
801       silc_mutex_unlock(queue->lock);
802       return;
803     }
804
805     next = queue->task;
806     
807     while(1) {
808       if (next->valid)
809         next->valid = FALSE;
810       if (queue->task == next->next)
811         break;
812       next = next->next;
813     }
814
815     silc_mutex_unlock(queue->lock);
816     return;
817   }
818
819   SILC_LOG_DEBUG(("Unregistering task"));
820
821   silc_mutex_lock(queue->lock);
822
823   /* Unregister the specific task */
824   if (task->valid)
825     task->valid = FALSE;
826
827   silc_mutex_unlock(queue->lock);
828 }
829
830 /* Remove task by fd */
831
832 void silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
833 {
834   SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
835
836   silc_task_del_by_fd(schedule->timeout_queue, fd);
837   silc_task_del_by_fd(schedule->fd_queue, fd);
838 }
839
840 /* Remove task by task callback. */
841
842 void silc_schedule_task_del_by_callback(SilcSchedule schedule,
843                                         SilcTaskCallback callback)
844 {
845   SILC_LOG_DEBUG(("Unregister task by callback"));
846
847   silc_task_del_by_callback(schedule->timeout_queue, callback);
848   silc_task_del_by_callback(schedule->fd_queue, callback);
849   silc_task_del_by_callback(schedule->generic_queue, callback);
850 }
851
852 /* Remove task by context. */
853
854 void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
855 {
856   SILC_LOG_DEBUG(("Unregister task by context"));
857
858   silc_task_del_by_context(schedule->timeout_queue, context);
859   silc_task_del_by_context(schedule->fd_queue, context);
860   silc_task_del_by_context(schedule->generic_queue, context);
861 }
862
863 /* Sets a file descriptor to be listened by select() in scheduler. One can
864    call this directly if wanted. This can be called multiple times for
865    one file descriptor to set different iomasks. */
866
867 void silc_schedule_set_listen_fd(SilcSchedule schedule,
868                                  SilcUInt32 fd, SilcTaskEvent iomask)
869 {
870   int i;
871   bool found = FALSE;
872
873   if (!schedule->valid)
874     return;
875
876   SILC_SCHEDULE_LOCK(schedule);
877
878   for (i = 0; i < schedule->max_fd; i++)
879     if (schedule->fd_list[i].fd == fd) {
880       schedule->fd_list[i].fd = fd;
881       schedule->fd_list[i].events = iomask;
882       if (i > schedule->last_fd)
883         schedule->last_fd = i;
884       found = TRUE;
885       break;
886     }
887
888   if (!found)
889     for (i = 0; i < schedule->max_fd; i++)
890       if (schedule->fd_list[i].events == 0) {
891         schedule->fd_list[i].fd = fd;
892         schedule->fd_list[i].events = iomask;
893         if (i > schedule->last_fd)
894           schedule->last_fd = i;
895         break;
896       }
897
898   SILC_SCHEDULE_UNLOCK(schedule);
899 }
900
901 /* Removes a file descriptor from listen list. */
902
903 void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
904 {
905   int i;
906
907   SILC_SCHEDULE_LOCK(schedule);
908
909   SILC_LOG_DEBUG(("Unset listen fd %d", fd));
910
911   for (i = 0; i < schedule->max_fd; i++)
912     if (schedule->fd_list[i].fd == fd) {
913       schedule->fd_list[i].fd = 0;
914       schedule->fd_list[i].events = 0;
915       if (schedule->last_fd == i)
916         schedule->last_fd = schedule->max_fd - 1;
917       break;
918     }
919
920   SILC_SCHEDULE_UNLOCK(schedule);
921 }
922
923 /* Register a new signal */
924
925 void silc_schedule_signal_register(SilcSchedule schedule, SilcUInt32 signal,
926                                    SilcTaskCallback callback, void *context)
927 {
928   silc_schedule_internal_signal_register(schedule->internal, signal,
929                                          callback, context);
930 }
931
932 /* Unregister a new signal */
933
934 void silc_schedule_signal_unregister(SilcSchedule schedule, SilcUInt32 signal,
935                                      SilcTaskCallback callback, void *context)
936 {
937   silc_schedule_internal_signal_unregister(schedule->internal, signal,
938                                            callback, context);
939 }
940
941 /* Call signal indicated by `signal'. */
942
943 void silc_schedule_signal_call(SilcSchedule schedule, SilcUInt32 signal)
944 {
945   /* Mark that signals needs to be delivered later. */
946   silc_schedule_internal_signal_call(schedule->internal, signal);
947   schedule->signal_tasks = TRUE;
948 }
949
950 /* Allocates a newtask task queue into the scheduler */
951
952 static void silc_task_queue_alloc(SilcTaskQueue *queue)
953 {
954   *queue = silc_calloc(1, sizeof(**queue));
955   silc_mutex_alloc(&(*queue)->lock);
956 }
957
958 /* Free's a task queue. */
959
960 static void silc_task_queue_free(SilcTaskQueue queue)
961 {
962   silc_mutex_free(queue->lock);
963   memset(queue, 'F', sizeof(*queue));
964   silc_free(queue);
965 }
966
967 /* Return task by its fd. */
968
969 static SilcTask silc_task_find(SilcTaskQueue queue, SilcUInt32 fd)
970 {
971   SilcTask next;
972
973   if (!queue->task)
974     return NULL;
975
976   next = queue->task;
977
978   while (1) {
979     if (next->fd == fd)
980       return next;
981     if (queue->task == next->next)
982       return NULL;
983     next = next->next;
984   }
985
986   return NULL;
987 }
988
989 /* Adds a non-timeout task into the task queue. This function is used
990    by silc_task_register function. Returns a pointer to the registered 
991    task. */
992
993 static SilcTask silc_task_add(SilcTaskQueue queue, SilcTask newtask, 
994                               SilcTaskPriority priority)
995 {
996   SilcTask task, next, prev;
997
998   /* Take the first task in the queue */
999   task = queue->task;
1000
1001   switch(priority) {
1002   case SILC_TASK_PRI_LOW:
1003     /* Lowest priority. The task is added at the end of the list. */
1004     prev = task->prev;
1005     newtask->prev = prev;
1006     newtask->next = task;
1007     prev->next = newtask;
1008     task->prev = newtask;
1009     break;
1010   case SILC_TASK_PRI_NORMAL:
1011     /* Normal priority. The task is added before lower priority tasks
1012        but after tasks with higher priority. */
1013     prev = task->prev;
1014     while(prev != task) {
1015       if (prev->priority > SILC_TASK_PRI_LOW)
1016         break;
1017       prev = prev->prev;
1018     }
1019     if (prev == task) {
1020       /* There are only lower priorities in the list, we will
1021          sit before them and become the first task in the queue. */
1022       prev = task->prev;
1023       newtask->prev = prev;
1024       newtask->next = task;
1025       task->prev = newtask;
1026       prev->next = newtask;
1027
1028       /* We are now the first task in queue */
1029       queue->task = newtask;
1030     } else {
1031       /* Found a spot from the list, add the task to the list. */
1032       next = prev->next;
1033       newtask->prev = prev;
1034       newtask->next = next;
1035       prev->next = newtask;
1036       next->prev = newtask;
1037     }
1038     break;
1039   default:
1040     silc_free(newtask);
1041     return NULL;
1042   }
1043
1044   return newtask;
1045 }
1046
1047 /* Return the timeout task with smallest timeout. */
1048
1049 static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first)
1050 {
1051   SilcTask prev, task;
1052
1053   prev = first->prev;
1054
1055   if (first == prev)
1056     return first;
1057
1058   task = first;
1059   while (1) {
1060     if (first == prev)
1061       break;
1062
1063     if (silc_schedule_task_timeout_compare(&prev->timeout, &task->timeout))
1064       task = prev;
1065
1066     prev = prev->prev;
1067   }
1068
1069   return task;
1070 }
1071
1072 /* Adds a timeout task into the task queue. This function is used by
1073    silc_task_register function. Returns a pointer to the registered 
1074    task. Timeout tasks are sorted by their timeout value in ascending
1075    order. The priority matters if there are more than one task with
1076    same timeout. */
1077
1078 static SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask newtask,
1079                                       SilcTaskPriority priority)
1080 {
1081   SilcTask task, prev, next;
1082
1083   /* Take the first task in the queue */
1084   task = queue->task;
1085
1086   /* Take last task from the list */
1087   prev = task->prev;
1088     
1089   switch(priority) {
1090   case SILC_TASK_PRI_LOW:
1091     /* Lowest priority. The task is added at the end of the list. */
1092     while(prev != task) {
1093
1094       /* If we have longer timeout than with the task head of us
1095          we have found our spot. */
1096       if (silc_schedule_task_timeout_compare(&prev->timeout, 
1097                                              &newtask->timeout))
1098         break;
1099
1100       /* If we are equal size of timeout we will be after it. */
1101       if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
1102                                               &prev->timeout))
1103         break;
1104
1105       /* We have shorter timeout, compare to next one. */
1106       prev = prev->prev;
1107     }
1108     /* Found a spot from the list, add the task to the list. */
1109     next = prev->next;
1110     newtask->prev = prev;
1111     newtask->next = next;
1112     prev->next = newtask;
1113     next->prev = newtask;
1114     
1115     if (prev == task) {
1116       /* Check if we are going to be the first task in the queue */
1117       if (silc_schedule_task_timeout_compare(&prev->timeout, 
1118                                              &newtask->timeout))
1119         break;
1120       if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
1121                                               &prev->timeout))
1122         break;
1123
1124       /* We are now the first task in queue */
1125       queue->task = newtask;
1126     }
1127     break;
1128   case SILC_TASK_PRI_NORMAL:
1129     /* Normal priority. The task is added before lower priority tasks
1130        but after tasks with higher priority. */
1131     while(prev != task) {
1132
1133       /* If we have longer timeout than with the task head of us
1134          we have found our spot. */
1135       if (silc_schedule_task_timeout_compare(&prev->timeout, 
1136                                              &newtask->timeout))
1137         break;
1138
1139       /* If we are equal size of timeout, priority kicks in place. */
1140       if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
1141                                               &prev->timeout))
1142         if (prev->priority >= SILC_TASK_PRI_NORMAL)
1143           break;
1144
1145       /* We have shorter timeout or higher priority, compare to next one. */
1146       prev = prev->prev;
1147     }
1148     /* Found a spot from the list, add the task to the list. */
1149     next = prev->next;
1150     newtask->prev = prev;
1151     newtask->next = next;
1152     prev->next = newtask;
1153     next->prev = newtask;
1154     
1155     if (prev == task) {
1156       /* Check if we are going to be the first task in the queue */
1157       if (silc_schedule_task_timeout_compare(&prev->timeout, 
1158                                              &newtask->timeout))
1159         break;
1160       if (!silc_schedule_task_timeout_compare(&newtask->timeout, 
1161                                               &prev->timeout))
1162         if (prev->priority >= SILC_TASK_PRI_NORMAL)
1163           break;
1164
1165       /* We are now the first task in queue */
1166       queue->task = newtask;
1167     }
1168     break;
1169   default:
1170     silc_free(newtask);
1171     return NULL;
1172   }
1173
1174   return newtask;
1175 }
1176
1177 /* Removes (unregisters) a task from particular task queue. This function
1178    is used internally by scheduler. This must be called holding the 
1179    queue->lock. */
1180
1181 static int silc_schedule_task_remove(SilcTaskQueue queue, SilcTask task)
1182 {
1183   SilcTask first, old, next;
1184
1185   if (!queue || !task)
1186     return FALSE;
1187
1188   if (!queue->task) {
1189     return FALSE;
1190   }
1191
1192   first = queue->task;
1193
1194   /* Unregister all tasks in queue */
1195   if (task == SILC_ALL_TASKS) {
1196     SILC_LOG_DEBUG(("Removing all tasks at once"));
1197     next = first;
1198
1199     while(1) {
1200       old = next->next;
1201       silc_free(next);
1202       if (old == first)
1203         break;
1204       next = old;
1205     }
1206
1207     queue->task = NULL;
1208     return TRUE;
1209   }
1210
1211   SILC_LOG_DEBUG(("Removing task"));
1212
1213   /* Unregister the task */
1214   old = first;
1215   while(1) {
1216     if (old == task) {
1217       SilcTask prev, next;
1218
1219       prev = old->prev;
1220       next = old->next;
1221       prev->next = next;
1222       next->prev = prev;
1223
1224       if (prev == old && next == old)
1225         queue->task = NULL;
1226       if (queue->task == old)
1227         queue->task = silc_task_get_first(queue, next);
1228       
1229       silc_free(old);
1230       return TRUE;
1231     }
1232     old = old->prev;
1233
1234     if (old == first) {
1235       return FALSE;
1236     }
1237   }
1238 }
1239
1240 /* Compare two time values. If the first argument is smaller than the
1241    second this function returns TRUE. */
1242
1243 static int silc_schedule_task_timeout_compare(struct timeval *smaller, 
1244                                               struct timeval *bigger)
1245 {
1246   if ((smaller->tv_sec < bigger->tv_sec) ||
1247       ((smaller->tv_sec == bigger->tv_sec) &&
1248        (smaller->tv_usec < bigger->tv_usec)))
1249     return TRUE;
1250
1251   return FALSE;
1252 }
1253
1254 static void silc_task_del_by_fd(SilcTaskQueue queue, SilcUInt32 fd)
1255 {
1256   SilcTask next;
1257
1258   silc_mutex_lock(queue->lock);
1259
1260   if (!queue->task) {
1261     silc_mutex_unlock(queue->lock);
1262     return;
1263   }
1264
1265   next = queue->task;
1266
1267   while(1) {
1268     if (next->fd == fd)
1269       next->valid = FALSE;
1270     if (queue->task == next->next)
1271       break;
1272     next = next->next;
1273   }
1274
1275   silc_mutex_unlock(queue->lock);
1276 }
1277
1278 static void silc_task_del_by_callback(SilcTaskQueue queue,
1279                                       SilcTaskCallback callback)
1280 {
1281   SilcTask next;
1282
1283   silc_mutex_lock(queue->lock);
1284
1285   if (!queue->task) {
1286     silc_mutex_unlock(queue->lock);
1287     return;
1288   }
1289
1290   next = queue->task;
1291
1292   while(1) {
1293     if (next->callback == callback)
1294       next->valid = FALSE;
1295     if (queue->task == next->next)
1296       break;
1297     next = next->next;
1298   }
1299
1300   silc_mutex_unlock(queue->lock);
1301 }
1302
1303 static void silc_task_del_by_context(SilcTaskQueue queue, void *context)
1304 {
1305   SilcTask next;
1306
1307   silc_mutex_lock(queue->lock);
1308
1309   if (!queue->task) {
1310     silc_mutex_unlock(queue->lock);
1311     return;
1312   }
1313
1314   next = queue->task;
1315
1316   while(1) {
1317     if (next->context == context)
1318       next->valid = FALSE;
1319     if (queue->task == next->next)
1320       break;
1321     next = next->next;
1322   }
1323
1324   silc_mutex_unlock(queue->lock);
1325 }