Check fd for READ even too because generic tasks are called in loop
[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   SILC_LOG_DEBUG(("Registering new task, fd=%d type=%d priority=%d", fd, 
724                   type, priority));
725
726   queue = SILC_SCHEDULE_GET_QUEUE(type);
727     
728   /* If the task is generic task, we check whether this task has already
729      been registered. Generic tasks are registered only once and after that
730      the same task applies to all file descriptors to be registered. */
731   if (type == SILC_TASK_GENERIC) {
732     silc_mutex_lock(queue->lock);
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   newtask->fd = fd;
760   newtask->context = context;
761   newtask->callback = callback;
762   newtask->valid = TRUE;
763   newtask->priority = priority;
764   newtask->type = type;
765   newtask->next = newtask;
766   newtask->prev = newtask;
767
768   /* Create timeout if marked to be timeout task */
769   if (((seconds + useconds) > 0) && (type == SILC_TASK_TIMEOUT)) {
770     silc_gettimeofday(&newtask->timeout);
771     newtask->timeout.tv_sec += seconds + (useconds / 1000000L);
772     newtask->timeout.tv_usec += (useconds % 1000000L);
773     if (newtask->timeout.tv_usec > 999999L) {
774       newtask->timeout.tv_sec += 1;
775       newtask->timeout.tv_usec -= 1000000L;
776     }
777     timeout = TRUE;
778   }
779
780   /* If the task is non-timeout task we have to tell the scheduler that we
781      would like to have these tasks scheduled at some odd distant future. */
782   if (type != SILC_TASK_TIMEOUT)
783     silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ, FALSE);
784
785   silc_mutex_lock(queue->lock);
786
787   /* Is this first task of the queue? */
788   if (queue->task == NULL) {
789     queue->task = newtask;
790     silc_mutex_unlock(queue->lock);
791     return newtask;
792   }
793
794   if (timeout)
795     newtask = silc_task_add_timeout(queue, newtask, priority);
796   else
797     newtask = silc_task_add(queue, newtask, priority);
798
799   silc_mutex_unlock(queue->lock);
800
801   return newtask;
802 }
803
804 /* Removes a task from the scheduler */
805
806 void silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
807 {
808   SilcTaskQueue queue = SILC_SCHEDULE_GET_QUEUE(task->type);
809
810   /* Unregister all tasks */
811   if (task == SILC_ALL_TASKS) {
812     SilcTask next;
813     SILC_LOG_DEBUG(("Unregistering all tasks at once"));
814
815     silc_mutex_lock(queue->lock);
816
817     if (!queue->task) {
818       silc_mutex_unlock(queue->lock);
819       return;
820     }
821
822     next = queue->task;
823     
824     while(1) {
825       if (next->valid)
826         next->valid = FALSE;
827       if (queue->task == next->next)
828         break;
829       next = next->next;
830     }
831
832     silc_mutex_unlock(queue->lock);
833     return;
834   }
835
836   SILC_LOG_DEBUG(("Unregistering task"));
837
838   silc_mutex_lock(queue->lock);
839
840   /* Unregister the specific task */
841   if (task->valid)
842     task->valid = FALSE;
843
844   silc_mutex_unlock(queue->lock);
845 }
846
847 /* Remove task by fd */
848
849 void silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
850 {
851   SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
852
853   silc_task_del_by_fd(schedule->timeout_queue, fd);
854   silc_task_del_by_fd(schedule->fd_queue, fd);
855 }
856
857 /* Remove task by task callback. */
858
859 void silc_schedule_task_del_by_callback(SilcSchedule schedule,
860                                         SilcTaskCallback callback)
861 {
862   SILC_LOG_DEBUG(("Unregister task by callback"));
863
864   silc_task_del_by_callback(schedule->timeout_queue, callback);
865   silc_task_del_by_callback(schedule->fd_queue, callback);
866   silc_task_del_by_callback(schedule->generic_queue, callback);
867 }
868
869 /* Remove task by context. */
870
871 void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
872 {
873   SILC_LOG_DEBUG(("Unregister task by context"));
874
875   silc_task_del_by_context(schedule->timeout_queue, context);
876   silc_task_del_by_context(schedule->fd_queue, context);
877   silc_task_del_by_context(schedule->generic_queue, context);
878 }
879
880 /* Sets a file descriptor to be listened by select() in scheduler. One can
881    call this directly if wanted. This can be called multiple times for
882    one file descriptor to set different iomasks. */
883
884 void silc_schedule_set_listen_fd(SilcSchedule schedule, SilcUInt32 fd,
885                                  SilcTaskEvent mask, bool send_events)
886 {
887   int i;
888   bool found = FALSE;
889
890   if (!schedule->valid)
891     return;
892
893   SILC_SCHEDULE_LOCK(schedule);
894
895   for (i = 0; i < schedule->max_fd; i++)
896     if (schedule->fd_list[i].fd == fd) {
897       schedule->fd_list[i].fd = fd;
898       schedule->fd_list[i].events = mask;
899       if (i > schedule->last_fd)
900         schedule->last_fd = i;
901       found = TRUE;
902       if (send_events) {
903         schedule->fd_list[i].revents = mask;
904         silc_schedule_dispatch_nontimeout(schedule);
905       }
906       break;
907     }
908
909   if (!found)
910     for (i = 0; i < schedule->max_fd; i++)
911       if (schedule->fd_list[i].events == 0) {
912         schedule->fd_list[i].fd = fd;
913         schedule->fd_list[i].events = mask;
914         if (i > schedule->last_fd)
915           schedule->last_fd = i;
916         if (send_events) {
917           schedule->fd_list[i].revents = mask;
918           silc_schedule_dispatch_nontimeout(schedule);
919         }
920         break;
921       }
922
923   SILC_SCHEDULE_UNLOCK(schedule);
924 }
925
926 /* Removes a file descriptor from listen list. */
927
928 void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
929 {
930   int i;
931
932   SILC_SCHEDULE_LOCK(schedule);
933
934   SILC_LOG_DEBUG(("Unset listen fd %d", fd));
935
936   for (i = 0; i < schedule->max_fd; i++)
937     if (schedule->fd_list[i].fd == fd) {
938       schedule->fd_list[i].fd = 0;
939       schedule->fd_list[i].events = 0;
940       if (schedule->last_fd == i)
941         schedule->last_fd = schedule->max_fd - 1;
942       break;
943     }
944
945   SILC_SCHEDULE_UNLOCK(schedule);
946 }
947
948 /* Register a new signal */
949
950 void silc_schedule_signal_register(SilcSchedule schedule, SilcUInt32 signal,
951                                    SilcTaskCallback callback, void *context)
952 {
953   silc_schedule_internal_signal_register(schedule->internal, signal,
954                                          callback, context);
955 }
956
957 /* Unregister a new signal */
958
959 void silc_schedule_signal_unregister(SilcSchedule schedule, SilcUInt32 signal,
960                                      SilcTaskCallback callback, void *context)
961 {
962   silc_schedule_internal_signal_unregister(schedule->internal, signal,
963                                            callback, context);
964 }
965
966 /* Call signal indicated by `signal'. */
967
968 void silc_schedule_signal_call(SilcSchedule schedule, SilcUInt32 signal)
969 {
970   /* Mark that signals needs to be delivered later. */
971   silc_schedule_internal_signal_call(schedule->internal, signal);
972   schedule->signal_tasks = TRUE;
973 }
974
975 /* Allocates a newtask task queue into the scheduler */
976
977 static void silc_task_queue_alloc(SilcTaskQueue *queue)
978 {
979   *queue = silc_calloc(1, sizeof(**queue));
980   silc_mutex_alloc(&(*queue)->lock);
981 }
982
983 /* Free's a task queue. */
984
985 static void silc_task_queue_free(SilcTaskQueue queue)
986 {
987   silc_mutex_free(queue->lock);
988   memset(queue, 'F', sizeof(*queue));
989   silc_free(queue);
990 }
991
992 /* Return task by its fd. */
993
994 static SilcTask silc_task_find(SilcTaskQueue queue, SilcUInt32 fd)
995 {
996   SilcTask next;
997
998   if (!queue->task)
999     return NULL;
1000
1001   next = queue->task;
1002
1003   while (1) {
1004     if (next->fd == fd)
1005       return next;
1006     if (queue->task == next->next)
1007       return NULL;
1008     next = next->next;
1009   }
1010
1011   return NULL;
1012 }
1013
1014 /* Adds a non-timeout task into the task queue. This function is used
1015    by silc_task_register function. Returns a pointer to the registered 
1016    task. */
1017
1018 static SilcTask silc_task_add(SilcTaskQueue queue, SilcTask newtask, 
1019                               SilcTaskPriority priority)
1020 {
1021   SilcTask task, next, prev;
1022
1023   /* Take the first task in the queue */
1024   task = queue->task;
1025
1026   switch(priority) {
1027   case SILC_TASK_PRI_LOW:
1028     /* Lowest priority. The task is added at the end of the list. */
1029     prev = task->prev;
1030     newtask->prev = prev;
1031     newtask->next = task;
1032     prev->next = newtask;
1033     task->prev = newtask;
1034     break;
1035   case SILC_TASK_PRI_NORMAL:
1036     /* Normal priority. The task is added before lower priority tasks
1037        but after tasks with higher priority. */
1038     prev = task->prev;
1039     while(prev != task) {
1040       if (prev->priority > SILC_TASK_PRI_LOW)
1041         break;
1042       prev = prev->prev;
1043     }
1044     if (prev == task) {
1045       /* There are only lower priorities in the list, we will
1046          sit before them and become the first task in the queue. */
1047       prev = task->prev;
1048       newtask->prev = prev;
1049       newtask->next = task;
1050       task->prev = newtask;
1051       prev->next = newtask;
1052
1053       /* We are now the first task in queue */
1054       queue->task = newtask;
1055     } else {
1056       /* Found a spot from the list, add the task to the list. */
1057       next = prev->next;
1058       newtask->prev = prev;
1059       newtask->next = next;
1060       prev->next = newtask;
1061       next->prev = newtask;
1062     }
1063     break;
1064   default:
1065     silc_free(newtask);
1066     return NULL;
1067   }
1068
1069   return newtask;
1070 }
1071
1072 /* Return the timeout task with smallest timeout. */
1073
1074 static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first)
1075 {
1076   SilcTask prev, task;
1077
1078   prev = first->prev;
1079
1080   if (first == prev)
1081     return first;
1082
1083   task = first;
1084   while (1) {
1085     if (first == prev)
1086       break;
1087
1088     if (silc_compare_timeval(&prev->timeout, &task->timeout))
1089       task = prev;
1090
1091     prev = prev->prev;
1092   }
1093
1094   return task;
1095 }
1096
1097 /* Adds a timeout task into the task queue. This function is used by
1098    silc_task_register function. Returns a pointer to the registered 
1099    task. Timeout tasks are sorted by their timeout value in ascending
1100    order. The priority matters if there are more than one task with
1101    same timeout. */
1102
1103 static SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask newtask,
1104                                       SilcTaskPriority priority)
1105 {
1106   SilcTask task, prev, next;
1107
1108   /* Take the first task in the queue */
1109   task = queue->task;
1110
1111   /* Take last task from the list */
1112   prev = task->prev;
1113     
1114   switch(priority) {
1115   case SILC_TASK_PRI_LOW:
1116     /* Lowest priority. The task is added at the end of the list. */
1117     while(prev != task) {
1118
1119       /* If we have longer timeout than with the task head of us
1120          we have found our spot. */
1121       if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
1122         break;
1123
1124       /* If we are equal size of timeout we will be after it. */
1125       if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
1126         break;
1127
1128       /* We have shorter timeout, compare to next one. */
1129       prev = prev->prev;
1130     }
1131     /* Found a spot from the list, add the task to the list. */
1132     next = prev->next;
1133     newtask->prev = prev;
1134     newtask->next = next;
1135     prev->next = newtask;
1136     next->prev = newtask;
1137     
1138     if (prev == task) {
1139       /* Check if we are going to be the first task in the queue */
1140       if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
1141         break;
1142       if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
1143         break;
1144
1145       /* We are now the first task in queue */
1146       queue->task = newtask;
1147     }
1148     break;
1149   case SILC_TASK_PRI_NORMAL:
1150     /* Normal priority. The task is added before lower priority tasks
1151        but after tasks with higher priority. */
1152     while(prev != task) {
1153
1154       /* If we have longer timeout than with the task head of us
1155          we have found our spot. */
1156       if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
1157         break;
1158
1159       /* If we are equal size of timeout, priority kicks in place. */
1160       if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
1161         if (prev->priority >= SILC_TASK_PRI_NORMAL)
1162           break;
1163
1164       /* We have shorter timeout or higher priority, compare to next one. */
1165       prev = prev->prev;
1166     }
1167     /* Found a spot from the list, add the task to the list. */
1168     next = prev->next;
1169     newtask->prev = prev;
1170     newtask->next = next;
1171     prev->next = newtask;
1172     next->prev = newtask;
1173     
1174     if (prev == task) {
1175       /* Check if we are going to be the first task in the queue */
1176       if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
1177         break;
1178       if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
1179         if (prev->priority >= SILC_TASK_PRI_NORMAL)
1180           break;
1181
1182       /* We are now the first task in queue */
1183       queue->task = newtask;
1184     }
1185     break;
1186   default:
1187     silc_free(newtask);
1188     return NULL;
1189   }
1190
1191   return newtask;
1192 }
1193
1194 /* Removes (unregisters) a task from particular task queue. This function
1195    is used internally by scheduler. This must be called holding the 
1196    queue->lock. */
1197
1198 static int silc_schedule_task_remove(SilcTaskQueue queue, SilcTask task)
1199 {
1200   SilcTask first, old, next;
1201
1202   if (!queue || !task)
1203     return FALSE;
1204
1205   if (!queue->task) {
1206     return FALSE;
1207   }
1208
1209   first = queue->task;
1210
1211   /* Unregister all tasks in queue */
1212   if (task == SILC_ALL_TASKS) {
1213     SILC_LOG_DEBUG(("Removing all tasks at once"));
1214     next = first;
1215
1216     while(1) {
1217       old = next->next;
1218       silc_free(next);
1219       if (old == first)
1220         break;
1221       next = old;
1222     }
1223
1224     queue->task = NULL;
1225     return TRUE;
1226   }
1227
1228   SILC_LOG_DEBUG(("Removing task"));
1229
1230   /* Unregister the task */
1231   old = first;
1232   while(1) {
1233     if (old == task) {
1234       SilcTask prev, next;
1235
1236       prev = old->prev;
1237       next = old->next;
1238       prev->next = next;
1239       next->prev = prev;
1240
1241       if (prev == old && next == old)
1242         queue->task = NULL;
1243       if (queue->task == old)
1244         queue->task = silc_task_get_first(queue, next);
1245       
1246       silc_free(old);
1247       return TRUE;
1248     }
1249     old = old->prev;
1250
1251     if (old == first) {
1252       return FALSE;
1253     }
1254   }
1255 }
1256
1257 static void silc_task_del_by_fd(SilcTaskQueue queue, SilcUInt32 fd)
1258 {
1259   SilcTask next;
1260
1261   silc_mutex_lock(queue->lock);
1262
1263   if (!queue->task) {
1264     silc_mutex_unlock(queue->lock);
1265     return;
1266   }
1267
1268   next = queue->task;
1269
1270   while(1) {
1271     if (next->fd == fd)
1272       next->valid = FALSE;
1273     if (queue->task == next->next)
1274       break;
1275     next = next->next;
1276   }
1277
1278   silc_mutex_unlock(queue->lock);
1279 }
1280
1281 static void silc_task_del_by_callback(SilcTaskQueue queue,
1282                                       SilcTaskCallback callback)
1283 {
1284   SilcTask next;
1285
1286   silc_mutex_lock(queue->lock);
1287
1288   if (!queue->task) {
1289     silc_mutex_unlock(queue->lock);
1290     return;
1291   }
1292
1293   next = queue->task;
1294
1295   while(1) {
1296     if (next->callback == callback)
1297       next->valid = FALSE;
1298     if (queue->task == next->next)
1299       break;
1300     next = next->next;
1301   }
1302
1303   silc_mutex_unlock(queue->lock);
1304 }
1305
1306 static void silc_task_del_by_context(SilcTaskQueue queue, void *context)
1307 {
1308   SilcTask next;
1309
1310   silc_mutex_lock(queue->lock);
1311
1312   if (!queue->task) {
1313     silc_mutex_unlock(queue->lock);
1314     return;
1315   }
1316
1317   next = queue->task;
1318
1319   while(1) {
1320     if (next->context == context)
1321       next->valid = FALSE;
1322     if (queue->task == next->next)
1323       break;
1324     next = next->next;
1325   }
1326
1327   silc_mutex_unlock(queue->lock);
1328 }