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