5 Author: Pekka Riikonen <priikone@silcnet.org>
7 Copyright (C) 1998 - 2001 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; either version 2 of the License, or
12 (at your option) any later version.
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
22 #include "silcincludes.h"
23 #include "silcschedule_i.h"
25 /* Forward declarations */
26 typedef struct SilcTaskQueueStruct *SilcTaskQueue;
28 /* System specific routines. Implemented under unix/, win32/ and such. */
30 /* System specific select(). Returns same values as normal select(). */
31 int silc_select(SilcScheduleFd fds, SilcUInt32 fds_count,
32 struct timeval *timeout);
34 /* Initializes the platform specific scheduler. This for example initializes
35 the wakeup mechanism of the scheduler. In multi-threaded environment
36 the scheduler needs to be wakenup when tasks are added or removed from
37 the task queues. Returns context to the platform specific scheduler. */
38 void *silc_schedule_internal_init(SilcSchedule schedule, void *context);
40 /* Uninitializes the platform specific scheduler context. */
41 void silc_schedule_internal_uninit(void *context);
43 /* Wakes up the scheduler. This is platform specific routine */
44 void silc_schedule_internal_wakeup(void *context);
47 void silc_schedule_internal_signal_register(void *context,
49 SilcTaskCallback callback,
50 void *callback_context);
52 /* Unregister signal */
53 void silc_schedule_internal_signal_unregister(void *context,
55 SilcTaskCallback callback,
56 void *callback_context);
58 /* Mark signal to be called later. */
59 void silc_schedule_internal_signal_call(void *context, SilcUInt32 signal);
61 /* Call all signals */
62 void silc_schedule_internal_signals_call(void *context,
63 SilcSchedule schedule);
65 /* Block registered signals in scheduler. */
66 void silc_schedule_internal_signals_block(void *context);
68 /* Unblock registered signals in schedule. */
69 void silc_schedule_internal_signals_unblock(void *context);
71 /* Internal task management routines. */
73 static void silc_schedule_dispatch_timeout(SilcSchedule schedule,
75 static void silc_task_queue_alloc(SilcTaskQueue *queue);
76 static void silc_task_queue_free(SilcTaskQueue queue);
77 static SilcTask silc_task_find(SilcTaskQueue queue, SilcUInt32 fd);
78 static SilcTask silc_task_add(SilcTaskQueue queue, SilcTask newtask,
79 SilcTaskPriority priority);
80 static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first);
81 static SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask newtask,
82 SilcTaskPriority priority);
83 static int silc_schedule_task_remove(SilcTaskQueue queue, SilcTask task);
84 static int silc_schedule_task_timeout_compare(struct timeval *smaller,
85 struct timeval *bigger);
86 static void silc_task_del_by_context(SilcTaskQueue queue, void *context);
87 static void silc_task_del_by_callback(SilcTaskQueue queue,
88 SilcTaskCallback callback);
89 static void silc_task_del_by_fd(SilcTaskQueue queue, SilcUInt32 fd);
91 /* Returns the task queue by task type */
92 #define SILC_SCHEDULE_GET_QUEUE(type) \
93 (type == SILC_TASK_FD ? schedule->fd_queue : \
94 type == SILC_TASK_TIMEOUT ? schedule->timeout_queue : \
95 schedule->generic_queue)
97 /* Locks. These also blocks signals that we care about and thus guarantee
98 that while we are in scheduler no signals can happen. This way we can
99 synchronise signals with SILC Scheduler. */
100 #define SILC_SCHEDULE_LOCK(schedule) \
102 silc_schedule_internal_signals_block(schedule->internal); \
103 silc_mutex_lock(schedule->lock); \
105 #define SILC_SCHEDULE_UNLOCK(schedule) \
107 silc_mutex_unlock(schedule->lock); \
108 silc_schedule_internal_signals_unblock(schedule->internal); \
111 /* SILC Task object. Represents one task in the scheduler. */
112 struct SilcTaskStruct {
114 SilcTaskCallback callback; /* Task callback */
115 void *context; /* Task callback context */
116 struct timeval timeout; /* Set for timeout tasks */
117 unsigned int valid : 1; /* Set when task is valid */
118 unsigned int priority : 2; /* Priority of the task */
119 unsigned int type : 5; /* Type of the task */
121 /* Pointers forming doubly linked circular list */
122 struct SilcTaskStruct *next;
123 struct SilcTaskStruct *prev;
126 /* SILC Task Queue object. The queue holds all the tasks in the scheduler.
127 There are always three task queues in the scheduler. One for non-timeout
128 tasks (fd tasks performing tasks over specified file descriptor),
129 one for timeout tasks and one for generic tasks. */
130 struct SilcTaskQueueStruct {
131 SilcTask task; /* Pointer to all tasks */
132 struct timeval timeout; /* Current timeout */
133 SILC_MUTEX_DEFINE(lock); /* Queue's lock */
137 SILC Scheduler structure.
139 This is the actual schedule object in SILC. Both SILC client and server
140 uses this same scheduler. Actually, this scheduler could be used by any
141 program needing scheduling.
143 Following short description of the fields:
145 SilcTaskQueue fd_queue
147 Task queue hook for non-timeout tasks. Usually this means that these
148 tasks perform different kind of I/O on file descriptors. File
149 descriptors are usually network sockets but they actually can be
150 any file descriptors. This hook is initialized in silc_schedule_init
151 function. Timeout tasks should not be added to this queue because
152 they will never expire.
154 SilcTaskQueue timeout_queue
156 Task queue hook for timeout tasks. This hook is reserved specificly
157 for tasks with timeout. Non-timeout tasks should not be added to this
158 queue because they will never get scheduled. This hook is also
159 initialized in silc_schedule_init function.
161 SilcTaskQueue generic_queue
163 Task queue hook for generic tasks. This hook is reserved specificly
164 for generic tasks, tasks that apply to all file descriptors, except
165 to those that have specificly registered a non-timeout task. This hook
166 is also initialized in silc_schedule_init function.
168 SilcScheduleFd fd_list
170 List of file descriptors the scheduler is supposed to be listenning.
171 This is updated internally.
176 Size of the fd_list list. There can be `max_fd' many tasks in
177 the scheduler at once. The `last_fd' is the last valid entry
180 struct timeval *timeout;
182 Pointer to the schedules next timeout. Value of this timeout is
183 automatically updated in the silc_schedule function.
187 Marks validity of the scheduler. This is a boolean value. When this
188 is false the scheduler is terminated and the program will end. This
189 set to true when the scheduler is initialized with silc_schedule_init
195 File descriptor sets for select(). These are automatically managed
196 by the scheduler and should not be touched otherwise.
200 System specific scheduler context.
202 SILC_MUTEX_DEFINE(lock)
208 TRUE when tasks has been registered from signals. Next round in
209 scheduler will call the callbacks when this is TRUE.
212 struct SilcScheduleStruct {
213 void *app_context; /* Application specific context */
214 SilcTaskQueue fd_queue;
215 SilcTaskQueue timeout_queue;
216 SilcTaskQueue generic_queue;
217 SilcScheduleFd fd_list;
220 struct timeval *timeout;
223 SILC_MUTEX_DEFINE(lock);
228 /* Initializes the scheduler. This returns the scheduler context that
229 is given as arugment usually to all silc_schedule_* functions.
230 The `max_tasks' indicates the number of maximum tasks that the
231 scheduler can handle. The `app_context' is application specific
232 context that is delivered to task callbacks. */
234 SilcSchedule silc_schedule_init(int max_tasks, void *app_context)
236 SilcSchedule schedule;
238 SILC_LOG_DEBUG(("Initializing scheduler"));
240 schedule = silc_calloc(1, sizeof(*schedule));
242 /* Allocate three task queues, one for file descriptor based tasks,
243 one for timeout tasks and one for generic tasks. */
244 silc_task_queue_alloc(&schedule->fd_queue);
245 silc_task_queue_alloc(&schedule->timeout_queue);
246 silc_task_queue_alloc(&schedule->generic_queue);
251 /* Initialize the scheduler */
252 schedule->fd_list = silc_calloc(max_tasks, sizeof(*schedule->fd_list));
253 schedule->max_fd = max_tasks;
254 schedule->timeout = NULL;
255 schedule->valid = TRUE;
256 schedule->app_context = app_context;
258 /* Allocate scheduler lock */
259 silc_mutex_alloc(&schedule->lock);
261 /* Initialize the platform specific scheduler. */
262 schedule->internal = silc_schedule_internal_init(schedule, app_context);
267 /* Uninitializes the schedule. This is called when the program is ready
268 to end. This removes all tasks and task queues. Returns FALSE if the
269 scheduler could not be uninitialized. This happens when the scheduler
270 is still valid and silc_schedule_stop has not been called. */
272 bool silc_schedule_uninit(SilcSchedule schedule)
274 SILC_LOG_DEBUG(("Uninitializing scheduler"));
276 if (schedule->valid == TRUE)
279 /* Dispatch all timeouts before going away */
280 silc_mutex_lock(schedule->timeout_queue->lock);
281 silc_schedule_dispatch_timeout(schedule, TRUE);
282 silc_mutex_unlock(schedule->timeout_queue->lock);
284 /* Deliver signals before going away */
285 if (schedule->signal_tasks) {
286 SILC_SCHEDULE_UNLOCK(schedule);
287 silc_schedule_internal_signals_call(schedule->internal, schedule);
288 schedule->signal_tasks = FALSE;
289 SILC_SCHEDULE_LOCK(schedule);
292 /* Unregister all tasks */
293 silc_schedule_task_remove(schedule->fd_queue, SILC_ALL_TASKS);
294 silc_schedule_task_remove(schedule->timeout_queue, SILC_ALL_TASKS);
295 silc_schedule_task_remove(schedule->generic_queue, SILC_ALL_TASKS);
297 /* Unregister all task queues */
298 silc_task_queue_free(schedule->fd_queue);
299 silc_task_queue_free(schedule->timeout_queue);
300 silc_task_queue_free(schedule->generic_queue);
302 silc_free(schedule->fd_list);
304 /* Uninit the platform specific scheduler. */
305 silc_schedule_internal_uninit(schedule->internal);
307 silc_mutex_free(schedule->lock);
313 /* Enlarge the capabilities of the scheduler to handle tasks to `max_tasks'. */
315 bool silc_schedule_reinit(SilcSchedule schedule, int max_tasks)
317 SILC_SCHEDULE_LOCK(schedule);
318 if (schedule->max_fd <= max_tasks)
320 schedule->fd_list = silc_realloc(schedule->fd_list,
321 (sizeof(*schedule->fd_list) * max_tasks));
322 schedule->max_fd = max_tasks;
323 SILC_SCHEDULE_UNLOCK(schedule);
327 /* Stops the schedule even if it is not supposed to be stopped yet.
328 After calling this, one should call silc_schedule_uninit (after the
329 silc_schedule has returned). */
331 void silc_schedule_stop(SilcSchedule schedule)
333 SILC_LOG_DEBUG(("Stopping scheduler"));
334 SILC_SCHEDULE_LOCK(schedule);
335 schedule->valid = FALSE;
336 SILC_SCHEDULE_UNLOCK(schedule);
339 /* Executes nontimeout tasks. It then checks whether any of ther fd tasks
340 was signaled by the silc_select. If some task was not signaled then
341 all generic tasks are executed for that task. The generic tasks are
342 never executed for task that has explicit fd task set. */
343 /* This holds the schedule->lock and the queue locks. */
345 static void silc_schedule_dispatch_nontimeout(SilcSchedule schedule)
348 int i, last_fd = schedule->last_fd;
351 for (i = 0; i <= last_fd; i++) {
352 if (schedule->fd_list[i].events == 0)
355 fd = schedule->fd_list[i].fd;
357 /* First check whether this fd has task in the fd queue */
358 silc_mutex_lock(schedule->fd_queue->lock);
359 task = silc_task_find(schedule->fd_queue, fd);
361 /* If the task was found then execute its callbacks. If not then
362 execute all generic tasks for that fd. */
364 /* Validity of the task is checked always before and after
365 execution beacuse the task might have been unregistered
366 in the callback function, ie. it is not valid anymore. */
368 /* Is the task ready for reading */
369 if (task->valid && schedule->fd_list[i].revents & SILC_TASK_READ) {
370 silc_mutex_unlock(schedule->fd_queue->lock);
371 SILC_SCHEDULE_UNLOCK(schedule);
372 task->callback(schedule, schedule->app_context,
373 SILC_TASK_READ, task->fd, task->context);
374 SILC_SCHEDULE_LOCK(schedule);
375 silc_mutex_lock(schedule->fd_queue->lock);
378 /* Is the task ready for writing */
379 if (task->valid && schedule->fd_list[i].revents & SILC_TASK_WRITE) {
380 silc_mutex_unlock(schedule->fd_queue->lock);
381 SILC_SCHEDULE_UNLOCK(schedule);
382 task->callback(schedule, schedule->app_context,
383 SILC_TASK_WRITE, task->fd, task->context);
384 SILC_SCHEDULE_LOCK(schedule);
385 silc_mutex_lock(schedule->fd_queue->lock);
389 silc_schedule_task_remove(schedule->fd_queue, task);
391 silc_mutex_unlock(schedule->fd_queue->lock);
393 /* Run generic tasks for this fd. */
395 silc_mutex_unlock(schedule->fd_queue->lock);
397 silc_mutex_lock(schedule->generic_queue->lock);
398 if (!schedule->generic_queue->task) {
399 silc_mutex_unlock(schedule->generic_queue->lock);
403 task = schedule->generic_queue->task;
405 /* Validity of the task is checked always before and after
406 execution beacuse the task might have been unregistered
407 in the callback function, ie. it is not valid anymore. */
409 /* Is the task ready for reading */
410 if (task->valid && schedule->fd_list[i].revents & SILC_TASK_READ) {
411 silc_mutex_unlock(schedule->generic_queue->lock);
412 SILC_SCHEDULE_UNLOCK(schedule);
413 task->callback(schedule, schedule->app_context,
414 SILC_TASK_READ, fd, task->context);
415 SILC_SCHEDULE_LOCK(schedule);
416 silc_mutex_lock(schedule->generic_queue->lock);
419 /* Is the task ready for writing */
420 if (task->valid && schedule->fd_list[i].revents & SILC_TASK_WRITE) {
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_schedule_task_timeout_compare(&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_schedule_task_timeout_compare(&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 /* Calculate next timeout for silc_select(). This is the timeout value
611 when at earliest some of the timeout tasks expire. */
612 silc_mutex_lock(schedule->timeout_queue->lock);
613 silc_schedule_select_timeout(schedule);
614 silc_mutex_unlock(schedule->timeout_queue->lock);
616 if (timeout_usecs >= 0) {
618 timeout.tv_usec = timeout_usecs;
619 schedule->timeout = &timeout;
622 SILC_SCHEDULE_UNLOCK(schedule);
624 /* This is the main select(). The program blocks here until some
625 of the selected file descriptors change status or the selected
627 SILC_LOG_DEBUG(("Select"));
628 ret = silc_select(schedule->fd_list, schedule->last_fd + 1,
631 SILC_SCHEDULE_LOCK(schedule);
638 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
642 silc_mutex_lock(schedule->timeout_queue->lock);
643 silc_schedule_dispatch_timeout(schedule, FALSE);
644 silc_mutex_unlock(schedule->timeout_queue->lock);
647 /* There is some data available now */
648 SILC_LOG_DEBUG(("Running non-timeout tasks"));
649 silc_schedule_dispatch_nontimeout(schedule);
653 if (!schedule->is_locked)
654 SILC_SCHEDULE_UNLOCK(schedule);
659 /* The SILC scheduler. This is actually the main routine in SILC programs.
660 When this returns the program is to be ended. Before this function can
661 be called, one must call silc_schedule_init function. */
663 void silc_schedule(SilcSchedule schedule)
665 SILC_LOG_DEBUG(("Running scheduler"));
667 if (schedule->valid == FALSE) {
668 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
672 SILC_SCHEDULE_LOCK(schedule);
673 schedule->is_locked = TRUE;
675 /* Start the scheduler loop */
676 while (silc_schedule_one(schedule, -1))
679 SILC_SCHEDULE_UNLOCK(schedule);
682 /* Wakes up the scheduler. This is used only in multi-threaded
683 environments where threads may add new tasks or remove old tasks
684 from task queues. This is called to wake up the scheduler in the
685 main thread so that it detects the changes in the task queues.
686 If threads support is not compiled in this function has no effect.
687 Implementation of this function is platform specific. */
689 void silc_schedule_wakeup(SilcSchedule schedule)
692 SILC_LOG_DEBUG(("Wakeup scheduler"));
693 SILC_SCHEDULE_LOCK(schedule);
694 silc_schedule_internal_wakeup(schedule->internal);
695 SILC_SCHEDULE_UNLOCK(schedule);
699 /* Returns the application specific context that was saved into the
700 scheduler in silc_schedule_init function. The context is also
701 returned to application in task callback functions, but this function
702 may be used to get it as well if needed. */
704 void *silc_schedule_get_context(SilcSchedule schedule)
706 return schedule->app_context;
709 /* Add new task to the scheduler */
711 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
712 SilcTaskCallback callback, void *context,
713 long seconds, long useconds,
715 SilcTaskPriority priority)
721 if (!schedule->valid)
724 SILC_LOG_DEBUG(("Registering new task, fd=%d type=%d priority=%d", fd,
727 queue = SILC_SCHEDULE_GET_QUEUE(type);
729 /* If the task is generic task, we check whether this task has already
730 been registered. Generic tasks are registered only once and after that
731 the same task applies to all file descriptors to be registered. */
732 if (type == SILC_TASK_GENERIC) {
733 silc_mutex_lock(queue->lock);
736 SilcTask task = queue->task;
738 if ((task->callback == callback) && (task->context == context)) {
739 SILC_LOG_DEBUG(("Found matching generic task, using the match"));
741 silc_mutex_unlock(queue->lock);
743 /* Add the fd to be listened, the task found now applies to this
745 silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ);
749 if (queue->task == task->next)
756 silc_mutex_unlock(queue->lock);
759 newtask = silc_calloc(1, sizeof(*newtask));
761 newtask->context = context;
762 newtask->callback = callback;
763 newtask->valid = TRUE;
764 newtask->priority = priority;
765 newtask->type = type;
766 newtask->next = newtask;
767 newtask->prev = newtask;
769 /* Create timeout if marked to be timeout task */
770 if (((seconds + useconds) > 0) && (type == SILC_TASK_TIMEOUT)) {
771 silc_gettimeofday(&newtask->timeout);
772 newtask->timeout.tv_sec += seconds + (useconds / 1000000L);
773 newtask->timeout.tv_usec += (useconds % 1000000L);
774 if (newtask->timeout.tv_usec > 999999L) {
775 newtask->timeout.tv_sec += 1;
776 newtask->timeout.tv_usec -= 1000000L;
781 /* If the task is non-timeout task we have to tell the scheduler that we
782 would like to have these tasks scheduled at some odd distant future. */
783 if (type != SILC_TASK_TIMEOUT)
784 silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ);
786 silc_mutex_lock(queue->lock);
788 /* Is this first task of the queue? */
789 if (queue->task == NULL) {
790 queue->task = newtask;
791 silc_mutex_unlock(queue->lock);
796 newtask = silc_task_add_timeout(queue, newtask, priority);
798 newtask = silc_task_add(queue, newtask, priority);
800 silc_mutex_unlock(queue->lock);
805 /* Removes a task from the scheduler */
807 void silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
809 SilcTaskQueue queue = SILC_SCHEDULE_GET_QUEUE(task->type);
811 /* Unregister all tasks */
812 if (task == SILC_ALL_TASKS) {
814 SILC_LOG_DEBUG(("Unregistering all tasks at once"));
816 silc_mutex_lock(queue->lock);
819 silc_mutex_unlock(queue->lock);
828 if (queue->task == next->next)
833 silc_mutex_unlock(queue->lock);
837 SILC_LOG_DEBUG(("Unregistering task"));
839 silc_mutex_lock(queue->lock);
841 /* Unregister the specific task */
845 silc_mutex_unlock(queue->lock);
848 /* Remove task by fd */
850 void silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
852 SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
854 silc_task_del_by_fd(schedule->timeout_queue, fd);
855 silc_task_del_by_fd(schedule->fd_queue, fd);
858 /* Remove task by task callback. */
860 void silc_schedule_task_del_by_callback(SilcSchedule schedule,
861 SilcTaskCallback callback)
863 SILC_LOG_DEBUG(("Unregister task by callback"));
865 silc_task_del_by_callback(schedule->timeout_queue, callback);
866 silc_task_del_by_callback(schedule->fd_queue, callback);
867 silc_task_del_by_callback(schedule->generic_queue, callback);
870 /* Remove task by context. */
872 void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
874 SILC_LOG_DEBUG(("Unregister task by context"));
876 silc_task_del_by_context(schedule->timeout_queue, context);
877 silc_task_del_by_context(schedule->fd_queue, context);
878 silc_task_del_by_context(schedule->generic_queue, context);
881 /* Sets a file descriptor to be listened by select() in scheduler. One can
882 call this directly if wanted. This can be called multiple times for
883 one file descriptor to set different iomasks. */
885 void silc_schedule_set_listen_fd(SilcSchedule schedule,
886 SilcUInt32 fd, SilcTaskEvent iomask)
891 if (!schedule->valid)
894 SILC_SCHEDULE_LOCK(schedule);
896 for (i = 0; i < schedule->max_fd; i++)
897 if (schedule->fd_list[i].fd == fd) {
898 schedule->fd_list[i].fd = fd;
899 schedule->fd_list[i].events = iomask;
900 if (i > schedule->last_fd)
901 schedule->last_fd = i;
907 for (i = 0; i < schedule->max_fd; i++)
908 if (schedule->fd_list[i].events == 0) {
909 schedule->fd_list[i].fd = fd;
910 schedule->fd_list[i].events = iomask;
911 if (i > schedule->last_fd)
912 schedule->last_fd = i;
916 SILC_SCHEDULE_UNLOCK(schedule);
919 /* Removes a file descriptor from listen list. */
921 void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
925 SILC_SCHEDULE_LOCK(schedule);
927 SILC_LOG_DEBUG(("Unset listen fd %d", fd));
929 for (i = 0; i < schedule->max_fd; i++)
930 if (schedule->fd_list[i].fd == fd) {
931 schedule->fd_list[i].fd = 0;
932 schedule->fd_list[i].events = 0;
933 if (schedule->last_fd == i)
934 schedule->last_fd = schedule->max_fd - 1;
938 SILC_SCHEDULE_UNLOCK(schedule);
941 /* Register a new signal */
943 void silc_schedule_signal_register(SilcSchedule schedule, SilcUInt32 signal,
944 SilcTaskCallback callback, void *context)
946 silc_schedule_internal_signal_register(schedule->internal, signal,
950 /* Unregister a new signal */
952 void silc_schedule_signal_unregister(SilcSchedule schedule, SilcUInt32 signal,
953 SilcTaskCallback callback, void *context)
955 silc_schedule_internal_signal_unregister(schedule->internal, signal,
959 /* Call signal indicated by `signal'. */
961 void silc_schedule_signal_call(SilcSchedule schedule, SilcUInt32 signal)
963 /* Mark that signals needs to be delivered later. */
964 silc_schedule_internal_signal_call(schedule->internal, signal);
965 schedule->signal_tasks = TRUE;
968 /* Allocates a newtask task queue into the scheduler */
970 static void silc_task_queue_alloc(SilcTaskQueue *queue)
972 *queue = silc_calloc(1, sizeof(**queue));
973 silc_mutex_alloc(&(*queue)->lock);
976 /* Free's a task queue. */
978 static void silc_task_queue_free(SilcTaskQueue queue)
980 silc_mutex_free(queue->lock);
981 memset(queue, 'F', sizeof(*queue));
985 /* Return task by its fd. */
987 static SilcTask silc_task_find(SilcTaskQueue queue, SilcUInt32 fd)
999 if (queue->task == next->next)
1007 /* Adds a non-timeout task into the task queue. This function is used
1008 by silc_task_register function. Returns a pointer to the registered
1011 static SilcTask silc_task_add(SilcTaskQueue queue, SilcTask newtask,
1012 SilcTaskPriority priority)
1014 SilcTask task, next, prev;
1016 /* Take the first task in the queue */
1020 case SILC_TASK_PRI_LOW:
1021 /* Lowest priority. The task is added at the end of the list. */
1023 newtask->prev = prev;
1024 newtask->next = task;
1025 prev->next = newtask;
1026 task->prev = newtask;
1028 case SILC_TASK_PRI_NORMAL:
1029 /* Normal priority. The task is added before lower priority tasks
1030 but after tasks with higher priority. */
1032 while(prev != task) {
1033 if (prev->priority > SILC_TASK_PRI_LOW)
1038 /* There are only lower priorities in the list, we will
1039 sit before them and become the first task in the queue. */
1041 newtask->prev = prev;
1042 newtask->next = task;
1043 task->prev = newtask;
1044 prev->next = newtask;
1046 /* We are now the first task in queue */
1047 queue->task = newtask;
1049 /* Found a spot from the list, add the task to the list. */
1051 newtask->prev = prev;
1052 newtask->next = next;
1053 prev->next = newtask;
1054 next->prev = newtask;
1065 /* Return the timeout task with smallest timeout. */
1067 static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first)
1069 SilcTask prev, task;
1081 if (silc_schedule_task_timeout_compare(&prev->timeout, &task->timeout))
1090 /* Adds a timeout task into the task queue. This function is used by
1091 silc_task_register function. Returns a pointer to the registered
1092 task. Timeout tasks are sorted by their timeout value in ascending
1093 order. The priority matters if there are more than one task with
1096 static SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask newtask,
1097 SilcTaskPriority priority)
1099 SilcTask task, prev, next;
1101 /* Take the first task in the queue */
1104 /* Take last task from the list */
1108 case SILC_TASK_PRI_LOW:
1109 /* Lowest priority. The task is added at the end of the list. */
1110 while(prev != task) {
1112 /* If we have longer timeout than with the task head of us
1113 we have found our spot. */
1114 if (silc_schedule_task_timeout_compare(&prev->timeout,
1118 /* If we are equal size of timeout we will be after it. */
1119 if (!silc_schedule_task_timeout_compare(&newtask->timeout,
1123 /* We have shorter timeout, compare to next one. */
1126 /* Found a spot from the list, add the task to the list. */
1128 newtask->prev = prev;
1129 newtask->next = next;
1130 prev->next = newtask;
1131 next->prev = newtask;
1134 /* Check if we are going to be the first task in the queue */
1135 if (silc_schedule_task_timeout_compare(&prev->timeout,
1138 if (!silc_schedule_task_timeout_compare(&newtask->timeout,
1142 /* We are now the first task in queue */
1143 queue->task = newtask;
1146 case SILC_TASK_PRI_NORMAL:
1147 /* Normal priority. The task is added before lower priority tasks
1148 but after tasks with higher priority. */
1149 while(prev != task) {
1151 /* If we have longer timeout than with the task head of us
1152 we have found our spot. */
1153 if (silc_schedule_task_timeout_compare(&prev->timeout,
1157 /* If we are equal size of timeout, priority kicks in place. */
1158 if (!silc_schedule_task_timeout_compare(&newtask->timeout,
1160 if (prev->priority >= SILC_TASK_PRI_NORMAL)
1163 /* We have shorter timeout or higher priority, compare to next one. */
1166 /* Found a spot from the list, add the task to the list. */
1168 newtask->prev = prev;
1169 newtask->next = next;
1170 prev->next = newtask;
1171 next->prev = newtask;
1174 /* Check if we are going to be the first task in the queue */
1175 if (silc_schedule_task_timeout_compare(&prev->timeout,
1178 if (!silc_schedule_task_timeout_compare(&newtask->timeout,
1180 if (prev->priority >= SILC_TASK_PRI_NORMAL)
1183 /* We are now the first task in queue */
1184 queue->task = newtask;
1195 /* Removes (unregisters) a task from particular task queue. This function
1196 is used internally by scheduler. This must be called holding the
1199 static int silc_schedule_task_remove(SilcTaskQueue queue, SilcTask task)
1201 SilcTask first, old, next;
1203 if (!queue || !task)
1210 first = queue->task;
1212 /* Unregister all tasks in queue */
1213 if (task == SILC_ALL_TASKS) {
1214 SILC_LOG_DEBUG(("Removing all tasks at once"));
1229 SILC_LOG_DEBUG(("Removing task"));
1231 /* Unregister the task */
1235 SilcTask prev, next;
1242 if (prev == old && next == old)
1244 if (queue->task == old)
1245 queue->task = silc_task_get_first(queue, next);
1258 /* Compare two time values. If the first argument is smaller than the
1259 second this function returns TRUE. */
1261 static int silc_schedule_task_timeout_compare(struct timeval *smaller,
1262 struct timeval *bigger)
1264 if ((smaller->tv_sec < bigger->tv_sec) ||
1265 ((smaller->tv_sec == bigger->tv_sec) &&
1266 (smaller->tv_usec < bigger->tv_usec)))
1272 static void silc_task_del_by_fd(SilcTaskQueue queue, SilcUInt32 fd)
1276 silc_mutex_lock(queue->lock);
1279 silc_mutex_unlock(queue->lock);
1287 next->valid = FALSE;
1288 if (queue->task == next->next)
1293 silc_mutex_unlock(queue->lock);
1296 static void silc_task_del_by_callback(SilcTaskQueue queue,
1297 SilcTaskCallback callback)
1301 silc_mutex_lock(queue->lock);
1304 silc_mutex_unlock(queue->lock);
1311 if (next->callback == callback)
1312 next->valid = FALSE;
1313 if (queue->task == next->next)
1318 silc_mutex_unlock(queue->lock);
1321 static void silc_task_del_by_context(SilcTaskQueue queue, void *context)
1325 silc_mutex_lock(queue->lock);
1328 silc_mutex_unlock(queue->lock);
1335 if (next->context == context)
1336 next->valid = FALSE;
1337 if (queue->task == next->next)
1342 silc_mutex_unlock(queue->lock);