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);
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 SilcTaskQueue fd_queue;
214 SilcTaskQueue timeout_queue;
215 SilcTaskQueue generic_queue;
216 SilcScheduleFd fd_list;
219 struct timeval *timeout;
222 SILC_MUTEX_DEFINE(lock);
227 /* Initializes the scheduler. This returns the scheduler context that
228 is given as arugment usually to all silc_schedule_* functions.
229 The `max_tasks' indicates the number of maximum tasks that the
230 scheduler can handle. */
232 SilcSchedule silc_schedule_init(int max_tasks)
234 SilcSchedule schedule;
236 SILC_LOG_DEBUG(("Initializing scheduler"));
238 schedule = silc_calloc(1, sizeof(*schedule));
240 /* Allocate three task queues, one for file descriptor based tasks,
241 one for timeout tasks and one for generic tasks. */
242 silc_task_queue_alloc(&schedule->fd_queue);
243 silc_task_queue_alloc(&schedule->timeout_queue);
244 silc_task_queue_alloc(&schedule->generic_queue);
249 /* Initialize the scheduler */
250 schedule->fd_list = silc_calloc(max_tasks, sizeof(*schedule->fd_list));
251 schedule->max_fd = max_tasks;
252 schedule->timeout = NULL;
253 schedule->valid = TRUE;
255 /* Allocate scheduler lock */
256 silc_mutex_alloc(&schedule->lock);
258 /* Initialize the platform specific scheduler. */
259 schedule->internal = silc_schedule_internal_init(schedule);
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_mutex_lock(schedule->timeout_queue->lock);
278 silc_schedule_dispatch_timeout(schedule, TRUE);
279 silc_mutex_unlock(schedule->timeout_queue->lock);
281 /* Deliver signals before going away */
282 if (schedule->signal_tasks) {
283 SILC_SCHEDULE_UNLOCK(schedule);
284 silc_schedule_internal_signals_call(schedule->internal, schedule);
285 schedule->signal_tasks = FALSE;
286 SILC_SCHEDULE_LOCK(schedule);
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)
345 int i, last_fd = schedule->last_fd;
348 for (i = 0; i <= last_fd; i++) {
349 if (schedule->fd_list[i].events == 0)
352 fd = schedule->fd_list[i].fd;
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);
358 /* If the task was found then execute its callbacks. If not then
359 execute all generic tasks for that fd. */
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. */
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, SILC_TASK_READ, task->fd, task->context);
370 SILC_SCHEDULE_LOCK(schedule);
371 silc_mutex_lock(schedule->fd_queue->lock);
374 /* Is the task ready for writing */
375 if (task->valid && schedule->fd_list[i].revents & SILC_TASK_WRITE) {
376 silc_mutex_unlock(schedule->fd_queue->lock);
377 SILC_SCHEDULE_UNLOCK(schedule);
378 task->callback(schedule, SILC_TASK_WRITE, task->fd, task->context);
379 SILC_SCHEDULE_LOCK(schedule);
380 silc_mutex_lock(schedule->fd_queue->lock);
384 silc_schedule_task_remove(schedule->fd_queue, task);
386 silc_mutex_unlock(schedule->fd_queue->lock);
388 /* Run generic tasks for this fd. */
390 silc_mutex_unlock(schedule->fd_queue->lock);
392 silc_mutex_lock(schedule->generic_queue->lock);
393 if (!schedule->generic_queue->task) {
394 silc_mutex_unlock(schedule->generic_queue->lock);
398 task = schedule->generic_queue->task;
400 /* Validity of the task is checked always before and after
401 execution beacuse the task might have been unregistered
402 in the callback function, ie. it is not valid anymore. */
404 /* Is the task ready for reading */
405 if (task->valid && schedule->fd_list[i].revents & SILC_TASK_READ) {
406 silc_mutex_unlock(schedule->generic_queue->lock);
407 SILC_SCHEDULE_UNLOCK(schedule);
408 task->callback(schedule, SILC_TASK_READ, fd, task->context);
409 SILC_SCHEDULE_LOCK(schedule);
410 silc_mutex_lock(schedule->generic_queue->lock);
413 /* Is the task ready for writing */
414 if (task->valid && schedule->fd_list[i].revents & SILC_TASK_WRITE) {
415 silc_mutex_unlock(schedule->generic_queue->lock);
416 SILC_SCHEDULE_UNLOCK(schedule);
417 task->callback(schedule, SILC_TASK_WRITE, fd, task->context);
418 SILC_SCHEDULE_LOCK(schedule);
419 silc_mutex_lock(schedule->generic_queue->lock);
423 /* Invalid (unregistered) tasks are removed from the
425 if (schedule->generic_queue->task == task->next) {
426 silc_schedule_task_remove(schedule->generic_queue, task);
427 silc_mutex_unlock(schedule->generic_queue->lock);
432 silc_schedule_task_remove(schedule->generic_queue, task);
436 /* Break if there isn't more tasks in the queue */
437 if (schedule->generic_queue->task == task->next)
443 silc_mutex_unlock(schedule->generic_queue->lock);
448 /* Executes all tasks whose timeout has expired. The task is removed from
449 the task queue after the callback function has returned. Also, invalid
450 tasks are removed here. We don't have to care about priorities because
451 tasks are already sorted in their priority order at the registration
453 /* This holds the schedule->lock and the schedule->timeout_queue->lock */
455 static void silc_schedule_dispatch_timeout(SilcSchedule schedule,
458 SilcTaskQueue queue = schedule->timeout_queue;
460 struct timeval curtime;
462 SILC_LOG_DEBUG(("Running timeout tasks"));
464 silc_gettimeofday(&curtime);
466 queue = schedule->timeout_queue;
467 if (queue && queue->task) {
470 /* Walk thorugh all tasks in the particular task queue and run all
471 the expired tasks. */
473 /* Execute the task if the timeout has expired */
475 silc_schedule_task_timeout_compare(&task->timeout, &curtime)) {
477 silc_mutex_unlock(queue->lock);
478 SILC_SCHEDULE_UNLOCK(schedule);
479 task->callback(schedule, SILC_TASK_EXPIRE, task->fd, task->context);
480 SILC_SCHEDULE_LOCK(schedule);
481 silc_mutex_lock(queue->lock);
484 /* Break if there isn't more tasks in the queue */
485 if (queue->task == task->next) {
486 silc_schedule_task_remove(queue, task);
492 /* Remove the task from queue */
493 silc_schedule_task_remove(queue, task->prev);
495 /* The timeout hasn't expired, check for next one */
497 /* Break if there isn't more tasks in the queue */
498 if (queue->task == task->next)
507 /* Calculates next timeout for select(). This is the timeout value
508 when at earliest some of the timeout tasks expire. If this is in the
509 past, they will be run now. */
510 /* This holds the schedule->lock and the schedule->timeout_queue->lock */
512 static void silc_schedule_select_timeout(SilcSchedule schedule)
514 SilcTaskQueue queue = schedule->timeout_queue;
516 struct timeval curtime;
518 /* Get the current time */
519 silc_gettimeofday(&curtime);
520 schedule->timeout = NULL;
522 /* First task in the task queue has always the smallest timeout. */
525 if (task && task->valid == TRUE) {
526 /* If the timeout is in past, we will run the task and all other
527 timeout tasks from the past. */
528 if (silc_schedule_task_timeout_compare(&task->timeout, &curtime)) {
529 silc_schedule_dispatch_timeout(schedule, FALSE);
531 /* The task(s) has expired and doesn't exist on the task queue
532 anymore. We continue with new timeout. */
533 queue = schedule->timeout_queue;
535 if (task == NULL || task->valid == FALSE)
539 /* Calculate the next timeout for select() */
540 queue->timeout.tv_sec = task->timeout.tv_sec - curtime.tv_sec;
541 queue->timeout.tv_usec = task->timeout.tv_usec - curtime.tv_usec;
542 if (queue->timeout.tv_sec < 0)
543 queue->timeout.tv_sec = 0;
545 /* We wouldn't want to go under zero, check for it. */
546 if (queue->timeout.tv_usec < 0) {
547 queue->timeout.tv_sec -= 1;
548 if (queue->timeout.tv_sec < 0)
549 queue->timeout.tv_sec = 0;
550 queue->timeout.tv_usec += 1000000L;
553 /* We've got the timeout value */
556 /* Task is not valid, remove it and try next one. */
557 silc_schedule_task_remove(queue, task);
559 if (queue->task == NULL)
564 /* Save the timeout */
566 schedule->timeout = &queue->timeout;
567 SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout->tv_sec,
568 schedule->timeout->tv_usec));
572 /* Runs the scheduler once and then returns. */
574 bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
576 struct timeval timeout;
579 SILC_LOG_DEBUG(("In scheduler loop"));
581 if (!schedule->is_locked)
582 SILC_SCHEDULE_LOCK(schedule);
584 /* Deliver signals if any has been set to be called */
585 if (schedule->signal_tasks) {
586 SILC_SCHEDULE_UNLOCK(schedule);
587 silc_schedule_internal_signals_call(schedule->internal, schedule);
588 schedule->signal_tasks = FALSE;
589 SILC_SCHEDULE_LOCK(schedule);
592 /* If the task queues aren't initialized or we aren't valid anymore
594 if ((!schedule->fd_queue && !schedule->timeout_queue
595 && !schedule->generic_queue) || schedule->valid == FALSE) {
596 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
597 if (!schedule->is_locked)
598 SILC_SCHEDULE_UNLOCK(schedule);
602 /* Calculate next timeout for silc_select(). This is the timeout value
603 when at earliest some of the timeout tasks expire. */
604 silc_mutex_lock(schedule->timeout_queue->lock);
605 silc_schedule_select_timeout(schedule);
606 silc_mutex_unlock(schedule->timeout_queue->lock);
608 if (timeout_usecs >= 0) {
610 timeout.tv_usec = timeout_usecs;
611 schedule->timeout = &timeout;
614 SILC_SCHEDULE_UNLOCK(schedule);
616 /* This is the main select(). The program blocks here until some
617 of the selected file descriptors change status or the selected
619 SILC_LOG_DEBUG(("Select"));
620 ret = silc_select(schedule->fd_list, schedule->last_fd + 1,
623 SILC_SCHEDULE_LOCK(schedule);
630 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
634 silc_mutex_lock(schedule->timeout_queue->lock);
635 silc_schedule_dispatch_timeout(schedule, FALSE);
636 silc_mutex_unlock(schedule->timeout_queue->lock);
639 /* There is some data available now */
640 SILC_LOG_DEBUG(("Running non-timeout tasks"));
641 silc_schedule_dispatch_nontimeout(schedule);
645 if (!schedule->is_locked)
646 SILC_SCHEDULE_UNLOCK(schedule);
651 /* The SILC scheduler. This is actually the main routine in SILC programs.
652 When this returns the program is to be ended. Before this function can
653 be called, one must call silc_schedule_init function. */
655 void silc_schedule(SilcSchedule schedule)
657 SILC_LOG_DEBUG(("Running scheduler"));
659 if (schedule->valid == FALSE) {
660 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
664 SILC_SCHEDULE_LOCK(schedule);
665 schedule->is_locked = TRUE;
667 /* Start the scheduler loop */
668 while (silc_schedule_one(schedule, -1))
671 SILC_SCHEDULE_UNLOCK(schedule);
674 /* Wakes up the scheduler. This is used only in multi-threaded
675 environments where threads may add new tasks or remove old tasks
676 from task queues. This is called to wake up the scheduler in the
677 main thread so that it detects the changes in the task queues.
678 If threads support is not compiled in this function has no effect.
679 Implementation of this function is platform specific. */
681 void silc_schedule_wakeup(SilcSchedule schedule)
684 SILC_LOG_DEBUG(("Wakeup scheduler"));
685 SILC_SCHEDULE_LOCK(schedule);
686 silc_schedule_internal_wakeup(schedule->internal);
687 SILC_SCHEDULE_UNLOCK(schedule);
691 /* Add new task to the scheduler */
693 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
694 SilcTaskCallback callback, void *context,
695 long seconds, long useconds,
697 SilcTaskPriority priority)
703 if (!schedule->valid)
706 SILC_LOG_DEBUG(("Registering new task, fd=%d type=%d priority=%d", fd,
709 queue = SILC_SCHEDULE_GET_QUEUE(type);
711 /* If the task is generic task, we check whether this task has already
712 been registered. Generic tasks are registered only once and after that
713 the same task applies to all file descriptors to be registered. */
714 if (type == SILC_TASK_GENERIC) {
715 silc_mutex_lock(queue->lock);
718 SilcTask task = queue->task;
720 if ((task->callback == callback) && (task->context == context)) {
721 SILC_LOG_DEBUG(("Found matching generic task, using the match"));
723 silc_mutex_unlock(queue->lock);
725 /* Add the fd to be listened, the task found now applies to this
727 silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ);
731 if (queue->task == task->next)
738 silc_mutex_unlock(queue->lock);
741 newtask = silc_calloc(1, sizeof(*newtask));
743 newtask->context = context;
744 newtask->callback = callback;
745 newtask->valid = TRUE;
746 newtask->priority = priority;
747 newtask->type = type;
748 newtask->next = newtask;
749 newtask->prev = newtask;
751 /* Create timeout if marked to be timeout task */
752 if (((seconds + useconds) > 0) && (type == SILC_TASK_TIMEOUT)) {
753 silc_gettimeofday(&newtask->timeout);
754 newtask->timeout.tv_sec += seconds + (useconds / 1000000L);
755 newtask->timeout.tv_usec += (useconds % 1000000L);
756 if (newtask->timeout.tv_usec > 999999L) {
757 newtask->timeout.tv_sec += 1;
758 newtask->timeout.tv_usec -= 1000000L;
763 /* If the task is non-timeout task we have to tell the scheduler that we
764 would like to have these tasks scheduled at some odd distant future. */
765 if (type != SILC_TASK_TIMEOUT)
766 silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ);
768 silc_mutex_lock(queue->lock);
770 /* Is this first task of the queue? */
771 if (queue->task == NULL) {
772 queue->task = newtask;
773 silc_mutex_unlock(queue->lock);
778 newtask = silc_task_add_timeout(queue, newtask, priority);
780 newtask = silc_task_add(queue, newtask, priority);
782 silc_mutex_unlock(queue->lock);
787 /* Removes a task from the scheduler */
789 void silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
791 SilcTaskQueue queue = SILC_SCHEDULE_GET_QUEUE(task->type);
793 /* Unregister all tasks */
794 if (task == SILC_ALL_TASKS) {
796 SILC_LOG_DEBUG(("Unregistering all tasks at once"));
798 silc_mutex_lock(queue->lock);
801 silc_mutex_unlock(queue->lock);
810 if (queue->task == next->next)
815 silc_mutex_unlock(queue->lock);
819 SILC_LOG_DEBUG(("Unregistering task"));
821 silc_mutex_lock(queue->lock);
823 /* Unregister the specific task */
827 silc_mutex_unlock(queue->lock);
830 /* Remove task by fd */
832 void silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
834 SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
836 silc_task_del_by_fd(schedule->timeout_queue, fd);
837 silc_task_del_by_fd(schedule->fd_queue, fd);
840 /* Remove task by task callback. */
842 void silc_schedule_task_del_by_callback(SilcSchedule schedule,
843 SilcTaskCallback callback)
845 SILC_LOG_DEBUG(("Unregister task by callback"));
847 silc_task_del_by_callback(schedule->timeout_queue, callback);
848 silc_task_del_by_callback(schedule->fd_queue, callback);
849 silc_task_del_by_callback(schedule->generic_queue, callback);
852 /* Remove task by context. */
854 void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
856 SILC_LOG_DEBUG(("Unregister task by context"));
858 silc_task_del_by_context(schedule->timeout_queue, context);
859 silc_task_del_by_context(schedule->fd_queue, context);
860 silc_task_del_by_context(schedule->generic_queue, context);
863 /* Sets a file descriptor to be listened by select() in scheduler. One can
864 call this directly if wanted. This can be called multiple times for
865 one file descriptor to set different iomasks. */
867 void silc_schedule_set_listen_fd(SilcSchedule schedule,
868 SilcUInt32 fd, SilcTaskEvent iomask)
873 if (!schedule->valid)
876 SILC_SCHEDULE_LOCK(schedule);
878 for (i = 0; i < schedule->max_fd; i++)
879 if (schedule->fd_list[i].fd == fd) {
880 schedule->fd_list[i].fd = fd;
881 schedule->fd_list[i].events = iomask;
882 if (i > schedule->last_fd)
883 schedule->last_fd = i;
889 for (i = 0; i < schedule->max_fd; i++)
890 if (schedule->fd_list[i].events == 0) {
891 schedule->fd_list[i].fd = fd;
892 schedule->fd_list[i].events = iomask;
893 if (i > schedule->last_fd)
894 schedule->last_fd = i;
898 SILC_SCHEDULE_UNLOCK(schedule);
901 /* Removes a file descriptor from listen list. */
903 void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
907 SILC_SCHEDULE_LOCK(schedule);
909 SILC_LOG_DEBUG(("Unset listen fd %d", fd));
911 for (i = 0; i < schedule->max_fd; i++)
912 if (schedule->fd_list[i].fd == fd) {
913 schedule->fd_list[i].fd = 0;
914 schedule->fd_list[i].events = 0;
915 if (schedule->last_fd == i)
916 schedule->last_fd = schedule->max_fd - 1;
920 SILC_SCHEDULE_UNLOCK(schedule);
923 /* Register a new signal */
925 void silc_schedule_signal_register(SilcSchedule schedule, SilcUInt32 signal,
926 SilcTaskCallback callback, void *context)
928 silc_schedule_internal_signal_register(schedule->internal, signal,
932 /* Unregister a new signal */
934 void silc_schedule_signal_unregister(SilcSchedule schedule, SilcUInt32 signal,
935 SilcTaskCallback callback, void *context)
937 silc_schedule_internal_signal_unregister(schedule->internal, signal,
941 /* Call signal indicated by `signal'. */
943 void silc_schedule_signal_call(SilcSchedule schedule, SilcUInt32 signal)
945 /* Mark that signals needs to be delivered later. */
946 silc_schedule_internal_signal_call(schedule->internal, signal);
947 schedule->signal_tasks = TRUE;
950 /* Allocates a newtask task queue into the scheduler */
952 static void silc_task_queue_alloc(SilcTaskQueue *queue)
954 *queue = silc_calloc(1, sizeof(**queue));
955 silc_mutex_alloc(&(*queue)->lock);
958 /* Free's a task queue. */
960 static void silc_task_queue_free(SilcTaskQueue queue)
962 silc_mutex_free(queue->lock);
963 memset(queue, 'F', sizeof(*queue));
967 /* Return task by its fd. */
969 static SilcTask silc_task_find(SilcTaskQueue queue, SilcUInt32 fd)
981 if (queue->task == next->next)
989 /* Adds a non-timeout task into the task queue. This function is used
990 by silc_task_register function. Returns a pointer to the registered
993 static SilcTask silc_task_add(SilcTaskQueue queue, SilcTask newtask,
994 SilcTaskPriority priority)
996 SilcTask task, next, prev;
998 /* Take the first task in the queue */
1002 case SILC_TASK_PRI_LOW:
1003 /* Lowest priority. The task is added at the end of the list. */
1005 newtask->prev = prev;
1006 newtask->next = task;
1007 prev->next = newtask;
1008 task->prev = newtask;
1010 case SILC_TASK_PRI_NORMAL:
1011 /* Normal priority. The task is added before lower priority tasks
1012 but after tasks with higher priority. */
1014 while(prev != task) {
1015 if (prev->priority > SILC_TASK_PRI_LOW)
1020 /* There are only lower priorities in the list, we will
1021 sit before them and become the first task in the queue. */
1023 newtask->prev = prev;
1024 newtask->next = task;
1025 task->prev = newtask;
1026 prev->next = newtask;
1028 /* We are now the first task in queue */
1029 queue->task = newtask;
1031 /* Found a spot from the list, add the task to the list. */
1033 newtask->prev = prev;
1034 newtask->next = next;
1035 prev->next = newtask;
1036 next->prev = newtask;
1047 /* Return the timeout task with smallest timeout. */
1049 static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first)
1051 SilcTask prev, task;
1063 if (silc_schedule_task_timeout_compare(&prev->timeout, &task->timeout))
1072 /* Adds a timeout task into the task queue. This function is used by
1073 silc_task_register function. Returns a pointer to the registered
1074 task. Timeout tasks are sorted by their timeout value in ascending
1075 order. The priority matters if there are more than one task with
1078 static SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask newtask,
1079 SilcTaskPriority priority)
1081 SilcTask task, prev, next;
1083 /* Take the first task in the queue */
1086 /* Take last task from the list */
1090 case SILC_TASK_PRI_LOW:
1091 /* Lowest priority. The task is added at the end of the list. */
1092 while(prev != task) {
1094 /* If we have longer timeout than with the task head of us
1095 we have found our spot. */
1096 if (silc_schedule_task_timeout_compare(&prev->timeout,
1100 /* If we are equal size of timeout we will be after it. */
1101 if (!silc_schedule_task_timeout_compare(&newtask->timeout,
1105 /* We have shorter timeout, compare to next one. */
1108 /* Found a spot from the list, add the task to the list. */
1110 newtask->prev = prev;
1111 newtask->next = next;
1112 prev->next = newtask;
1113 next->prev = newtask;
1116 /* Check if we are going to be the first task in the queue */
1117 if (silc_schedule_task_timeout_compare(&prev->timeout,
1120 if (!silc_schedule_task_timeout_compare(&newtask->timeout,
1124 /* We are now the first task in queue */
1125 queue->task = newtask;
1128 case SILC_TASK_PRI_NORMAL:
1129 /* Normal priority. The task is added before lower priority tasks
1130 but after tasks with higher priority. */
1131 while(prev != task) {
1133 /* If we have longer timeout than with the task head of us
1134 we have found our spot. */
1135 if (silc_schedule_task_timeout_compare(&prev->timeout,
1139 /* If we are equal size of timeout, priority kicks in place. */
1140 if (!silc_schedule_task_timeout_compare(&newtask->timeout,
1142 if (prev->priority >= SILC_TASK_PRI_NORMAL)
1145 /* We have shorter timeout or higher priority, compare to next one. */
1148 /* Found a spot from the list, add the task to the list. */
1150 newtask->prev = prev;
1151 newtask->next = next;
1152 prev->next = newtask;
1153 next->prev = newtask;
1156 /* Check if we are going to be the first task in the queue */
1157 if (silc_schedule_task_timeout_compare(&prev->timeout,
1160 if (!silc_schedule_task_timeout_compare(&newtask->timeout,
1162 if (prev->priority >= SILC_TASK_PRI_NORMAL)
1165 /* We are now the first task in queue */
1166 queue->task = newtask;
1177 /* Removes (unregisters) a task from particular task queue. This function
1178 is used internally by scheduler. This must be called holding the
1181 static int silc_schedule_task_remove(SilcTaskQueue queue, SilcTask task)
1183 SilcTask first, old, next;
1185 if (!queue || !task)
1192 first = queue->task;
1194 /* Unregister all tasks in queue */
1195 if (task == SILC_ALL_TASKS) {
1196 SILC_LOG_DEBUG(("Removing all tasks at once"));
1211 SILC_LOG_DEBUG(("Removing task"));
1213 /* Unregister the task */
1217 SilcTask prev, next;
1224 if (prev == old && next == old)
1226 if (queue->task == old)
1227 queue->task = silc_task_get_first(queue, next);
1240 /* Compare two time values. If the first argument is smaller than the
1241 second this function returns TRUE. */
1243 static int silc_schedule_task_timeout_compare(struct timeval *smaller,
1244 struct timeval *bigger)
1246 if ((smaller->tv_sec < bigger->tv_sec) ||
1247 ((smaller->tv_sec == bigger->tv_sec) &&
1248 (smaller->tv_usec < bigger->tv_usec)))
1254 static void silc_task_del_by_fd(SilcTaskQueue queue, SilcUInt32 fd)
1258 silc_mutex_lock(queue->lock);
1261 silc_mutex_unlock(queue->lock);
1269 next->valid = FALSE;
1270 if (queue->task == next->next)
1275 silc_mutex_unlock(queue->lock);
1278 static void silc_task_del_by_callback(SilcTaskQueue queue,
1279 SilcTaskCallback callback)
1283 silc_mutex_lock(queue->lock);
1286 silc_mutex_unlock(queue->lock);
1293 if (next->callback == callback)
1294 next->valid = FALSE;
1295 if (queue->task == next->next)
1300 silc_mutex_unlock(queue->lock);
1303 static void silc_task_del_by_context(SilcTaskQueue queue, void *context)
1307 silc_mutex_lock(queue->lock);
1310 silc_mutex_unlock(queue->lock);
1317 if (next->context == context)
1318 next->valid = FALSE;
1319 if (queue->task == next->next)
1324 silc_mutex_unlock(queue->lock);