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