5 Author: Pekka Riikonen <priikone@silcnet.org>
7 Copyright (C) 1998 - 2005 Pekka Riikonen
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.
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.
21 #include "silcincludes.h"
22 #include "silcschedule_i.h"
24 /* Forward declarations */
25 typedef struct SilcTaskQueueStruct *SilcTaskQueue;
27 /* System specific routines. Implemented under unix/, win32/ and such. */
29 /* System specific select(). Returns same values as normal select(). */
30 int silc_select(SilcScheduleFd fds, SilcUInt32 fds_count,
31 struct timeval *timeout);
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);
39 /* Uninitializes the platform specific scheduler context. */
40 void silc_schedule_internal_uninit(void *context);
42 /* Wakes up the scheduler. This is platform specific routine */
43 void silc_schedule_internal_wakeup(void *context);
46 void silc_schedule_internal_signal_register(void *context,
48 SilcTaskCallback callback,
49 void *callback_context);
51 /* Unregister signal */
52 void silc_schedule_internal_signal_unregister(void *context,
54 SilcTaskCallback callback,
55 void *callback_context);
57 /* Mark signal to be called later. */
58 void silc_schedule_internal_signal_call(void *context, SilcUInt32 signal);
60 /* Call all signals */
61 void silc_schedule_internal_signals_call(void *context,
62 SilcSchedule schedule);
64 /* Block registered signals in scheduler. */
65 void silc_schedule_internal_signals_block(void *context);
67 /* Unblock registered signals in schedule. */
68 void silc_schedule_internal_signals_unblock(void *context);
70 /* Internal task management routines. */
72 static void silc_schedule_dispatch_timeout(SilcSchedule schedule,
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);
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)
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) \
99 silc_schedule_internal_signals_block(schedule->internal); \
100 silc_mutex_lock(schedule->lock); \
102 #define SILC_SCHEDULE_UNLOCK(schedule) \
104 silc_mutex_unlock(schedule->lock); \
105 silc_schedule_internal_signals_unblock(schedule->internal); \
108 /* SILC Task object. Represents one task in the scheduler. */
109 struct SilcTaskStruct {
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 */
118 /* Pointers forming doubly linked circular list */
119 struct SilcTaskStruct *next;
120 struct SilcTaskStruct *prev;
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 */
134 SILC Scheduler structure.
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.
140 Following short description of the fields:
142 SilcTaskQueue fd_queue
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.
151 SilcTaskQueue timeout_queue
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.
158 SilcTaskQueue generic_queue
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.
165 SilcScheduleFd fd_list
167 List of file descriptors the scheduler is supposed to be listenning.
168 This is updated internally.
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
177 struct timeval *timeout;
179 Pointer to the schedules next timeout. Value of this timeout is
180 automatically updated in the silc_schedule function.
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
192 File descriptor sets for select(). These are automatically managed
193 by the scheduler and should not be touched otherwise.
197 System specific scheduler context.
199 SILC_MUTEX_DEFINE(lock)
205 TRUE when tasks has been registered from signals. Next round in
206 scheduler will call the callbacks when this is TRUE.
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;
217 struct timeval *timeout;
220 SILC_MUTEX_DEFINE(lock);
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. */
231 SilcSchedule silc_schedule_init(int max_tasks, void *app_context)
233 SilcSchedule schedule;
235 SILC_LOG_DEBUG(("Initializing scheduler"));
237 schedule = silc_calloc(1, sizeof(*schedule));
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);
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;
255 /* Allocate scheduler lock */
256 silc_mutex_alloc(&schedule->lock);
258 /* Initialize the platform specific scheduler. */
259 schedule->internal = silc_schedule_internal_init(schedule, app_context);
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. */
269 bool silc_schedule_uninit(SilcSchedule schedule)
271 SILC_LOG_DEBUG(("Uninitializing scheduler"));
273 if (schedule->valid == TRUE)
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);
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;
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);
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);
299 silc_free(schedule->fd_list);
301 /* Uninit the platform specific scheduler. */
302 silc_schedule_internal_uninit(schedule->internal);
304 silc_mutex_free(schedule->lock);
310 /* Enlarge the capabilities of the scheduler to handle tasks to `max_tasks'. */
312 bool silc_schedule_reinit(SilcSchedule schedule, int max_tasks)
314 SILC_SCHEDULE_LOCK(schedule);
315 if (schedule->max_fd <= max_tasks)
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);
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). */
328 void silc_schedule_stop(SilcSchedule schedule)
330 SILC_LOG_DEBUG(("Stopping scheduler"));
331 SILC_SCHEDULE_LOCK(schedule);
332 schedule->valid = FALSE;
333 SILC_SCHEDULE_UNLOCK(schedule);
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. */
342 static void silc_schedule_dispatch_nontimeout(SilcSchedule schedule)
346 SilcUInt32 fd, last_fd = schedule->last_fd;
349 for (i = 0; i <= last_fd; i++) {
350 if (schedule->fd_list[i].events == 0)
353 /* First check whether this fd has task in the fd queue */
354 silc_mutex_lock(schedule->fd_queue->lock);
355 fd = schedule->fd_list[i].fd;
356 task = silc_task_find(schedule->fd_queue, fd);
357 revents = schedule->fd_list[i].revents;
359 /* If the task was found then execute its callbacks. If not then
360 execute all generic tasks for that fd. */
362 /* Validity of the task is checked always before and after
363 execution beacuse the task might have been unregistered
364 in the callback function, ie. it is not valid anymore. */
366 /* Is the task ready for reading */
367 if (task->valid && revents & SILC_TASK_READ) {
368 silc_mutex_unlock(schedule->fd_queue->lock);
369 SILC_SCHEDULE_UNLOCK(schedule);
370 task->callback(schedule, schedule->app_context,
371 SILC_TASK_READ, task->fd, task->context);
372 SILC_SCHEDULE_LOCK(schedule);
373 silc_mutex_lock(schedule->fd_queue->lock);
376 /* Is the task ready for writing */
377 if (task->valid && revents & SILC_TASK_WRITE) {
378 silc_mutex_unlock(schedule->fd_queue->lock);
379 SILC_SCHEDULE_UNLOCK(schedule);
380 task->callback(schedule, schedule->app_context,
381 SILC_TASK_WRITE, task->fd, task->context);
382 SILC_SCHEDULE_LOCK(schedule);
383 silc_mutex_lock(schedule->fd_queue->lock);
387 silc_schedule_task_remove(schedule->fd_queue, task);
389 silc_mutex_unlock(schedule->fd_queue->lock);
391 /* Run generic tasks for this fd. */
393 silc_mutex_unlock(schedule->fd_queue->lock);
395 silc_mutex_lock(schedule->generic_queue->lock);
396 if (!schedule->generic_queue->task) {
397 silc_mutex_unlock(schedule->generic_queue->lock);
401 task = schedule->generic_queue->task;
403 /* Validity of the task and fd is checked always before and after
404 execution beacuse the task might have been unregistered
405 in the callback function, ie. it is not valid anymore. */
407 /* Is the task ready for reading */
408 if (task->valid && revents & SILC_TASK_READ &&
409 fd == schedule->fd_list[i].fd) {
410 silc_mutex_unlock(schedule->generic_queue->lock);
411 SILC_SCHEDULE_UNLOCK(schedule);
412 task->callback(schedule, schedule->app_context,
413 SILC_TASK_READ, fd, task->context);
414 SILC_SCHEDULE_LOCK(schedule);
415 silc_mutex_lock(schedule->generic_queue->lock);
418 /* Is the task ready for writing */
419 if (task->valid && revents & SILC_TASK_WRITE &&
420 fd == schedule->fd_list[i].fd) {
421 silc_mutex_unlock(schedule->generic_queue->lock);
422 SILC_SCHEDULE_UNLOCK(schedule);
423 task->callback(schedule, schedule->app_context,
424 SILC_TASK_WRITE, fd, task->context);
425 SILC_SCHEDULE_LOCK(schedule);
426 silc_mutex_lock(schedule->generic_queue->lock);
430 /* Invalid (unregistered) tasks are removed from the
432 if (schedule->generic_queue->task == task->next) {
433 silc_schedule_task_remove(schedule->generic_queue, task);
434 silc_mutex_unlock(schedule->generic_queue->lock);
439 silc_schedule_task_remove(schedule->generic_queue, task);
443 /* Break if there isn't more tasks in the queue */
444 if (schedule->generic_queue->task == task->next)
450 silc_mutex_unlock(schedule->generic_queue->lock);
455 /* Executes all tasks whose timeout has expired. The task is removed from
456 the task queue after the callback function has returned. Also, invalid
457 tasks are removed here. We don't have to care about priorities because
458 tasks are already sorted in their priority order at the registration
460 /* This holds the schedule->lock and the schedule->timeout_queue->lock */
462 static void silc_schedule_dispatch_timeout(SilcSchedule schedule,
465 SilcTaskQueue queue = schedule->timeout_queue;
467 struct timeval curtime;
469 SILC_LOG_DEBUG(("Running timeout tasks"));
471 silc_gettimeofday(&curtime);
473 queue = schedule->timeout_queue;
474 if (queue && queue->task) {
477 /* Walk thorugh all tasks in the particular task queue and run all
478 the expired tasks. */
480 /* Execute the task if the timeout has expired */
482 silc_compare_timeval(&task->timeout, &curtime)) {
484 silc_mutex_unlock(queue->lock);
485 SILC_SCHEDULE_UNLOCK(schedule);
486 task->callback(schedule, schedule->app_context,
487 SILC_TASK_EXPIRE, task->fd, task->context);
488 SILC_SCHEDULE_LOCK(schedule);
489 silc_mutex_lock(queue->lock);
492 /* Break if there isn't more tasks in the queue */
493 if (queue->task == task->next) {
494 silc_schedule_task_remove(queue, task);
500 /* Remove the task from queue */
501 silc_schedule_task_remove(queue, task->prev);
503 /* The timeout hasn't expired, check for next one */
505 /* Break if there isn't more tasks in the queue */
506 if (queue->task == task->next)
515 /* Calculates next timeout for select(). This is the timeout value
516 when at earliest some of the timeout tasks expire. If this is in the
517 past, they will be run now. */
518 /* This holds the schedule->lock and the schedule->timeout_queue->lock */
520 static void silc_schedule_select_timeout(SilcSchedule schedule)
522 SilcTaskQueue queue = schedule->timeout_queue;
524 struct timeval curtime;
526 /* Get the current time */
527 silc_gettimeofday(&curtime);
528 schedule->timeout = NULL;
530 /* First task in the task queue has always the smallest timeout. */
533 if (task && task->valid == TRUE) {
534 /* If the timeout is in past, we will run the task and all other
535 timeout tasks from the past. */
536 if (silc_compare_timeval(&task->timeout, &curtime)) {
537 silc_schedule_dispatch_timeout(schedule, FALSE);
539 /* The task(s) has expired and doesn't exist on the task queue
540 anymore. We continue with new timeout. */
541 queue = schedule->timeout_queue;
543 if (task == NULL || task->valid == FALSE)
547 /* Calculate the next timeout for select() */
548 queue->timeout.tv_sec = task->timeout.tv_sec - curtime.tv_sec;
549 queue->timeout.tv_usec = task->timeout.tv_usec - curtime.tv_usec;
550 if (queue->timeout.tv_sec < 0)
551 queue->timeout.tv_sec = 0;
553 /* We wouldn't want to go under zero, check for it. */
554 if (queue->timeout.tv_usec < 0) {
555 queue->timeout.tv_sec -= 1;
556 if (queue->timeout.tv_sec < 0)
557 queue->timeout.tv_sec = 0;
558 queue->timeout.tv_usec += 1000000L;
561 /* We've got the timeout value */
564 /* Task is not valid, remove it and try next one. */
565 silc_schedule_task_remove(queue, task);
567 if (queue->task == NULL)
572 /* Save the timeout */
574 schedule->timeout = &queue->timeout;
575 SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout->tv_sec,
576 schedule->timeout->tv_usec));
580 /* Runs the scheduler once and then returns. */
582 bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
584 struct timeval timeout;
587 SILC_LOG_DEBUG(("In scheduler loop"));
589 if (!schedule->is_locked)
590 SILC_SCHEDULE_LOCK(schedule);
592 /* Deliver signals if any has been set to be called */
593 if (schedule->signal_tasks) {
594 SILC_SCHEDULE_UNLOCK(schedule);
595 silc_schedule_internal_signals_call(schedule->internal, schedule);
596 schedule->signal_tasks = FALSE;
597 SILC_SCHEDULE_LOCK(schedule);
600 /* If the task queues aren't initialized or we aren't valid anymore
602 if ((!schedule->fd_queue && !schedule->timeout_queue
603 && !schedule->generic_queue) || schedule->valid == FALSE) {
604 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
605 if (!schedule->is_locked)
606 SILC_SCHEDULE_UNLOCK(schedule);
610 /* If the task queues aren't initialized or we aren't valid anymore
612 if ((!schedule->fd_queue && !schedule->timeout_queue
613 && !schedule->generic_queue) || schedule->valid == FALSE) {
614 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
615 if (!schedule->is_locked)
616 SILC_SCHEDULE_UNLOCK(schedule);
620 /* Calculate next timeout for silc_select(). This is the timeout value
621 when at earliest some of the timeout tasks expire. */
622 silc_mutex_lock(schedule->timeout_queue->lock);
623 silc_schedule_select_timeout(schedule);
624 silc_mutex_unlock(schedule->timeout_queue->lock);
626 if (timeout_usecs >= 0) {
628 timeout.tv_usec = timeout_usecs;
629 schedule->timeout = &timeout;
632 SILC_SCHEDULE_UNLOCK(schedule);
634 /* This is the main select(). The program blocks here until some
635 of the selected file descriptors change status or the selected
637 SILC_LOG_DEBUG(("Select"));
638 ret = silc_select(schedule->fd_list, schedule->last_fd + 1,
641 SILC_SCHEDULE_LOCK(schedule);
648 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
652 silc_mutex_lock(schedule->timeout_queue->lock);
653 silc_schedule_dispatch_timeout(schedule, FALSE);
654 silc_mutex_unlock(schedule->timeout_queue->lock);
657 /* There is some data available now */
658 SILC_LOG_DEBUG(("Running non-timeout tasks"));
659 silc_schedule_dispatch_nontimeout(schedule);
663 if (!schedule->is_locked)
664 SILC_SCHEDULE_UNLOCK(schedule);
669 /* The SILC scheduler. This is actually the main routine in SILC programs.
670 When this returns the program is to be ended. Before this function can
671 be called, one must call silc_schedule_init function. */
673 void silc_schedule(SilcSchedule schedule)
675 SILC_LOG_DEBUG(("Running scheduler"));
677 if (schedule->valid == FALSE) {
678 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
682 SILC_SCHEDULE_LOCK(schedule);
683 schedule->is_locked = TRUE;
685 /* Start the scheduler loop */
686 while (silc_schedule_one(schedule, -1))
689 SILC_SCHEDULE_UNLOCK(schedule);
692 /* Wakes up the scheduler. This is used only in multi-threaded
693 environments where threads may add new tasks or remove old tasks
694 from task queues. This is called to wake up the scheduler in the
695 main thread so that it detects the changes in the task queues.
696 If threads support is not compiled in this function has no effect.
697 Implementation of this function is platform specific. */
699 void silc_schedule_wakeup(SilcSchedule schedule)
702 SILC_LOG_DEBUG(("Wakeup scheduler"));
703 SILC_SCHEDULE_LOCK(schedule);
704 silc_schedule_internal_wakeup(schedule->internal);
705 SILC_SCHEDULE_UNLOCK(schedule);
709 /* Returns the application specific context that was saved into the
710 scheduler in silc_schedule_init function. The context is also
711 returned to application in task callback functions, but this function
712 may be used to get it as well if needed. */
714 void *silc_schedule_get_context(SilcSchedule schedule)
716 return schedule->app_context;
719 /* Add new task to the scheduler */
721 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
722 SilcTaskCallback callback, void *context,
723 long seconds, long useconds,
725 SilcTaskPriority priority)
731 if (!schedule->valid)
734 queue = SILC_SCHEDULE_GET_QUEUE(type);
736 /* If the task is generic task, we check whether this task has already
737 been registered. Generic tasks are registered only once and after that
738 the same task applies to all file descriptors to be registered. */
739 if (type == SILC_TASK_GENERIC) {
740 silc_mutex_lock(queue->lock);
742 SILC_LOG_DEBUG(("Registering new task, fd=%d type=%d priority=%d", fd,
746 SilcTask task = queue->task;
748 if ((task->callback == callback) && (task->context == context)) {
749 SILC_LOG_DEBUG(("Found matching generic task, using the match"));
751 silc_mutex_unlock(queue->lock);
753 /* Add the fd to be listened, the task found now applies to this
755 silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ, FALSE);
759 if (queue->task == task->next)
766 silc_mutex_unlock(queue->lock);
769 newtask = silc_calloc(1, sizeof(*newtask));
773 SILC_LOG_DEBUG(("Registering new task %p, fd=%d type=%d priority=%d",
774 newtask, fd, type, priority));
777 newtask->context = context;
778 newtask->callback = callback;
779 newtask->valid = TRUE;
780 newtask->priority = priority;
781 newtask->type = type;
782 newtask->next = newtask;
783 newtask->prev = newtask;
785 /* Create timeout if marked to be timeout task */
786 if (((seconds + useconds) > 0) && (type == SILC_TASK_TIMEOUT)) {
787 silc_gettimeofday(&newtask->timeout);
788 newtask->timeout.tv_sec += seconds + (useconds / 1000000L);
789 newtask->timeout.tv_usec += (useconds % 1000000L);
790 if (newtask->timeout.tv_usec > 999999L) {
791 newtask->timeout.tv_sec += 1;
792 newtask->timeout.tv_usec -= 1000000L;
797 /* If the task is non-timeout task we have to tell the scheduler that we
798 would like to have these tasks scheduled at some odd distant future. */
799 if (type != SILC_TASK_TIMEOUT)
800 silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ, FALSE);
802 silc_mutex_lock(queue->lock);
804 /* Is this first task of the queue? */
805 if (queue->task == NULL) {
806 queue->task = newtask;
807 silc_mutex_unlock(queue->lock);
812 newtask = silc_task_add_timeout(queue, newtask, priority);
814 newtask = silc_task_add(queue, newtask, priority);
816 silc_mutex_unlock(queue->lock);
821 /* Removes a task from the scheduler */
823 void silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
825 SilcTaskQueue queue = SILC_SCHEDULE_GET_QUEUE(task->type);
827 /* Unregister all tasks */
828 if (task == SILC_ALL_TASKS) {
830 SILC_LOG_DEBUG(("Unregistering all tasks at once"));
832 silc_mutex_lock(queue->lock);
835 silc_mutex_unlock(queue->lock);
844 if (queue->task == next->next)
849 silc_mutex_unlock(queue->lock);
853 SILC_LOG_DEBUG(("Unregistering task"));
855 silc_mutex_lock(queue->lock);
857 /* Unregister the specific task */
861 silc_mutex_unlock(queue->lock);
864 /* Remove task by fd */
866 void silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
868 SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
870 silc_task_del_by_fd(schedule->timeout_queue, fd);
871 silc_task_del_by_fd(schedule->fd_queue, fd);
874 /* Remove task by task callback. */
876 void silc_schedule_task_del_by_callback(SilcSchedule schedule,
877 SilcTaskCallback callback)
879 SILC_LOG_DEBUG(("Unregister task by callback"));
881 silc_task_del_by_callback(schedule->timeout_queue, callback);
882 silc_task_del_by_callback(schedule->fd_queue, callback);
883 silc_task_del_by_callback(schedule->generic_queue, callback);
886 /* Remove task by context. */
888 void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
890 SILC_LOG_DEBUG(("Unregister task by context"));
892 silc_task_del_by_context(schedule->timeout_queue, context);
893 silc_task_del_by_context(schedule->fd_queue, context);
894 silc_task_del_by_context(schedule->generic_queue, context);
897 /* Sets a file descriptor to be listened by select() in scheduler. One can
898 call this directly if wanted. This can be called multiple times for
899 one file descriptor to set different iomasks. */
901 void silc_schedule_set_listen_fd(SilcSchedule schedule, SilcUInt32 fd,
902 SilcTaskEvent mask, bool send_events)
907 if (!schedule->valid)
910 SILC_SCHEDULE_LOCK(schedule);
912 for (i = 0; i < schedule->max_fd; i++)
913 if (schedule->fd_list[i].fd == fd) {
914 schedule->fd_list[i].fd = fd;
915 schedule->fd_list[i].events = mask;
916 schedule->fd_list[i].revents = 0;
917 if (i > schedule->last_fd)
918 schedule->last_fd = i;
921 schedule->fd_list[i].revents = mask;
922 silc_schedule_dispatch_nontimeout(schedule);
928 for (i = 0; i < schedule->max_fd; i++)
929 if (schedule->fd_list[i].events == 0) {
930 schedule->fd_list[i].fd = fd;
931 schedule->fd_list[i].events = mask;
932 schedule->fd_list[i].revents = 0;
933 if (i > schedule->last_fd)
934 schedule->last_fd = i;
936 schedule->fd_list[i].revents = mask;
937 silc_schedule_dispatch_nontimeout(schedule);
942 SILC_SCHEDULE_UNLOCK(schedule);
945 /* Removes a file descriptor from listen list. */
947 void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
951 SILC_SCHEDULE_LOCK(schedule);
953 SILC_LOG_DEBUG(("Unset listen fd %d", fd));
955 for (i = 0; i < schedule->max_fd; i++)
956 if (schedule->fd_list[i].fd == fd) {
957 schedule->fd_list[i].fd = 0;
958 schedule->fd_list[i].events = 0;
959 schedule->fd_list[i].revents = 0;
960 if (schedule->last_fd == i && i > 0)
961 schedule->last_fd = i - 1;
965 SILC_SCHEDULE_UNLOCK(schedule);
968 /* Register a new signal */
970 void silc_schedule_signal_register(SilcSchedule schedule, SilcUInt32 signal,
971 SilcTaskCallback callback, void *context)
973 silc_schedule_internal_signal_register(schedule->internal, signal,
977 /* Unregister a new signal */
979 void silc_schedule_signal_unregister(SilcSchedule schedule, SilcUInt32 signal,
980 SilcTaskCallback callback, void *context)
982 silc_schedule_internal_signal_unregister(schedule->internal, signal,
986 /* Call signal indicated by `signal'. */
988 void silc_schedule_signal_call(SilcSchedule schedule, SilcUInt32 signal)
990 /* Mark that signals needs to be delivered later. */
991 silc_schedule_internal_signal_call(schedule->internal, signal);
992 schedule->signal_tasks = TRUE;
995 /* Allocates a newtask task queue into the scheduler */
997 static void silc_task_queue_alloc(SilcTaskQueue *queue)
999 *queue = silc_calloc(1, sizeof(**queue));
1000 silc_mutex_alloc(&(*queue)->lock);
1003 /* Free's a task queue. */
1005 static void silc_task_queue_free(SilcTaskQueue queue)
1007 silc_mutex_free(queue->lock);
1008 memset(queue, 'F', sizeof(*queue));
1012 /* Return task by its fd. */
1014 static SilcTask silc_task_find(SilcTaskQueue queue, SilcUInt32 fd)
1026 if (queue->task == next->next)
1034 /* Adds a non-timeout task into the task queue. This function is used
1035 by silc_task_register function. Returns a pointer to the registered
1038 static SilcTask silc_task_add(SilcTaskQueue queue, SilcTask newtask,
1039 SilcTaskPriority priority)
1041 SilcTask task, next, prev;
1043 /* Take the first task in the queue */
1047 case SILC_TASK_PRI_LOW:
1048 /* Lowest priority. The task is added at the end of the list. */
1050 newtask->prev = prev;
1051 newtask->next = task;
1052 prev->next = newtask;
1053 task->prev = newtask;
1055 case SILC_TASK_PRI_NORMAL:
1056 /* Normal priority. The task is added before lower priority tasks
1057 but after tasks with higher priority. */
1059 while(prev != task) {
1060 if (prev->priority > SILC_TASK_PRI_LOW)
1065 /* There are only lower priorities in the list, we will
1066 sit before them and become the first task in the queue. */
1068 newtask->prev = prev;
1069 newtask->next = task;
1070 task->prev = newtask;
1071 prev->next = newtask;
1073 /* We are now the first task in queue */
1074 queue->task = newtask;
1076 /* Found a spot from the list, add the task to the list. */
1078 newtask->prev = prev;
1079 newtask->next = next;
1080 prev->next = newtask;
1081 next->prev = newtask;
1092 /* Return the timeout task with smallest timeout. */
1094 static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first)
1096 SilcTask prev, task;
1108 if (silc_compare_timeval(&prev->timeout, &task->timeout))
1117 /* Adds a timeout task into the task queue. This function is used by
1118 silc_task_register function. Returns a pointer to the registered
1119 task. Timeout tasks are sorted by their timeout value in ascending
1120 order. The priority matters if there are more than one task with
1123 static SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask newtask,
1124 SilcTaskPriority priority)
1126 SilcTask task, prev, next;
1128 /* Take the first task in the queue */
1131 /* Take last task from the list */
1135 case SILC_TASK_PRI_LOW:
1136 /* Lowest priority. The task is added at the end of the list. */
1137 while(prev != task) {
1139 /* If we have longer timeout than with the task head of us
1140 we have found our spot. */
1141 if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
1144 /* If we are equal size of timeout we will be after it. */
1145 if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
1148 /* We have shorter timeout, compare to next one. */
1151 /* Found a spot from the list, add the task to the list. */
1153 newtask->prev = prev;
1154 newtask->next = next;
1155 prev->next = newtask;
1156 next->prev = newtask;
1159 /* Check if we are going to be the first task in the queue */
1160 if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
1162 if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
1165 /* We are now the first task in queue */
1166 queue->task = newtask;
1169 case SILC_TASK_PRI_NORMAL:
1170 /* Normal priority. The task is added before lower priority tasks
1171 but after tasks with higher priority. */
1172 while(prev != task) {
1174 /* If we have longer timeout than with the task head of us
1175 we have found our spot. */
1176 if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
1179 /* If we are equal size of timeout, priority kicks in place. */
1180 if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
1181 if (prev->priority >= SILC_TASK_PRI_NORMAL)
1184 /* We have shorter timeout or higher priority, compare to next one. */
1187 /* Found a spot from the list, add the task to the list. */
1189 newtask->prev = prev;
1190 newtask->next = next;
1191 prev->next = newtask;
1192 next->prev = newtask;
1195 /* Check if we are going to be the first task in the queue */
1196 if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
1198 if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
1199 if (prev->priority >= SILC_TASK_PRI_NORMAL)
1202 /* We are now the first task in queue */
1203 queue->task = newtask;
1214 /* Removes (unregisters) a task from particular task queue. This function
1215 is used internally by scheduler. This must be called holding the
1218 static int silc_schedule_task_remove(SilcTaskQueue queue, SilcTask task)
1220 SilcTask first, old, next;
1222 if (!queue || !task)
1229 first = queue->task;
1231 /* Unregister all tasks in queue */
1232 if (task == SILC_ALL_TASKS) {
1233 SILC_LOG_DEBUG(("Removing all tasks at once"));
1248 SILC_LOG_DEBUG(("Removing task %p", task));
1250 /* Unregister the task */
1254 SilcTask prev, next;
1261 if (prev == old && next == old)
1263 if (queue->task == old)
1264 queue->task = silc_task_get_first(queue, next);
1277 static void silc_task_del_by_fd(SilcTaskQueue queue, SilcUInt32 fd)
1281 silc_mutex_lock(queue->lock);
1284 silc_mutex_unlock(queue->lock);
1292 next->valid = FALSE;
1293 if (queue->task == next->next)
1298 silc_mutex_unlock(queue->lock);
1301 static void silc_task_del_by_callback(SilcTaskQueue queue,
1302 SilcTaskCallback callback)
1306 silc_mutex_lock(queue->lock);
1309 silc_mutex_unlock(queue->lock);
1316 if (next->callback == callback)
1317 next->valid = FALSE;
1318 if (queue->task == next->next)
1323 silc_mutex_unlock(queue->lock);
1326 static void silc_task_del_by_context(SilcTaskQueue queue, void *context)
1330 silc_mutex_lock(queue->lock);
1333 silc_mutex_unlock(queue->lock);
1340 if (next->context == context)
1341 next->valid = FALSE;
1342 if (queue->task == next->next)
1347 silc_mutex_unlock(queue->lock);