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