5 Author: Pekka Riikonen <priikone@silcnet.org>
7 Copyright (C) 1998 - 2002 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;
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, schedule->app_context,
370 SILC_TASK_READ, task->fd, task->context);
371 SILC_SCHEDULE_LOCK(schedule);
372 silc_mutex_lock(schedule->fd_queue->lock);
375 /* Is the task ready for writing */
376 if (task->valid && schedule->fd_list[i].revents & SILC_TASK_WRITE) {
377 silc_mutex_unlock(schedule->fd_queue->lock);
378 SILC_SCHEDULE_UNLOCK(schedule);
379 task->callback(schedule, schedule->app_context,
380 SILC_TASK_WRITE, task->fd, task->context);
381 SILC_SCHEDULE_LOCK(schedule);
382 silc_mutex_lock(schedule->fd_queue->lock);
386 silc_schedule_task_remove(schedule->fd_queue, task);
388 silc_mutex_unlock(schedule->fd_queue->lock);
390 /* Run generic tasks for this fd. */
392 silc_mutex_unlock(schedule->fd_queue->lock);
394 silc_mutex_lock(schedule->generic_queue->lock);
395 if (!schedule->generic_queue->task) {
396 silc_mutex_unlock(schedule->generic_queue->lock);
400 task = schedule->generic_queue->task;
402 /* Validity of the task is checked always before and after
403 execution beacuse the task might have been unregistered
404 in the callback function, ie. it is not valid anymore. */
406 /* Is the task ready for reading */
407 if (task->valid && schedule->fd_list[i].revents & SILC_TASK_READ) {
408 silc_mutex_unlock(schedule->generic_queue->lock);
409 SILC_SCHEDULE_UNLOCK(schedule);
410 task->callback(schedule, schedule->app_context,
411 SILC_TASK_READ, fd, task->context);
412 SILC_SCHEDULE_LOCK(schedule);
413 silc_mutex_lock(schedule->generic_queue->lock);
416 /* Is the task ready for writing */
417 if (task->valid && schedule->fd_list[i].revents & SILC_TASK_WRITE) {
418 silc_mutex_unlock(schedule->generic_queue->lock);
419 SILC_SCHEDULE_UNLOCK(schedule);
420 task->callback(schedule, schedule->app_context,
421 SILC_TASK_WRITE, fd, task->context);
422 SILC_SCHEDULE_LOCK(schedule);
423 silc_mutex_lock(schedule->generic_queue->lock);
427 /* Invalid (unregistered) tasks are removed from the
429 if (schedule->generic_queue->task == task->next) {
430 silc_schedule_task_remove(schedule->generic_queue, task);
431 silc_mutex_unlock(schedule->generic_queue->lock);
436 silc_schedule_task_remove(schedule->generic_queue, task);
440 /* Break if there isn't more tasks in the queue */
441 if (schedule->generic_queue->task == task->next)
447 silc_mutex_unlock(schedule->generic_queue->lock);
452 /* Executes all tasks whose timeout has expired. The task is removed from
453 the task queue after the callback function has returned. Also, invalid
454 tasks are removed here. We don't have to care about priorities because
455 tasks are already sorted in their priority order at the registration
457 /* This holds the schedule->lock and the schedule->timeout_queue->lock */
459 static void silc_schedule_dispatch_timeout(SilcSchedule schedule,
462 SilcTaskQueue queue = schedule->timeout_queue;
464 struct timeval curtime;
466 SILC_LOG_DEBUG(("Running timeout tasks"));
468 silc_gettimeofday(&curtime);
470 queue = schedule->timeout_queue;
471 if (queue && queue->task) {
474 /* Walk thorugh all tasks in the particular task queue and run all
475 the expired tasks. */
477 /* Execute the task if the timeout has expired */
479 silc_compare_timeval(&task->timeout, &curtime)) {
481 silc_mutex_unlock(queue->lock);
482 SILC_SCHEDULE_UNLOCK(schedule);
483 task->callback(schedule, schedule->app_context,
484 SILC_TASK_EXPIRE, task->fd, task->context);
485 SILC_SCHEDULE_LOCK(schedule);
486 silc_mutex_lock(queue->lock);
489 /* Break if there isn't more tasks in the queue */
490 if (queue->task == task->next) {
491 silc_schedule_task_remove(queue, task);
497 /* Remove the task from queue */
498 silc_schedule_task_remove(queue, task->prev);
500 /* The timeout hasn't expired, check for next one */
502 /* Break if there isn't more tasks in the queue */
503 if (queue->task == task->next)
512 /* Calculates next timeout for select(). This is the timeout value
513 when at earliest some of the timeout tasks expire. If this is in the
514 past, they will be run now. */
515 /* This holds the schedule->lock and the schedule->timeout_queue->lock */
517 static void silc_schedule_select_timeout(SilcSchedule schedule)
519 SilcTaskQueue queue = schedule->timeout_queue;
521 struct timeval curtime;
523 /* Get the current time */
524 silc_gettimeofday(&curtime);
525 schedule->timeout = NULL;
527 /* First task in the task queue has always the smallest timeout. */
530 if (task && task->valid == TRUE) {
531 /* If the timeout is in past, we will run the task and all other
532 timeout tasks from the past. */
533 if (silc_compare_timeval(&task->timeout, &curtime)) {
534 silc_schedule_dispatch_timeout(schedule, FALSE);
536 /* The task(s) has expired and doesn't exist on the task queue
537 anymore. We continue with new timeout. */
538 queue = schedule->timeout_queue;
540 if (task == NULL || task->valid == FALSE)
544 /* Calculate the next timeout for select() */
545 queue->timeout.tv_sec = task->timeout.tv_sec - curtime.tv_sec;
546 queue->timeout.tv_usec = task->timeout.tv_usec - curtime.tv_usec;
547 if (queue->timeout.tv_sec < 0)
548 queue->timeout.tv_sec = 0;
550 /* We wouldn't want to go under zero, check for it. */
551 if (queue->timeout.tv_usec < 0) {
552 queue->timeout.tv_sec -= 1;
553 if (queue->timeout.tv_sec < 0)
554 queue->timeout.tv_sec = 0;
555 queue->timeout.tv_usec += 1000000L;
558 /* We've got the timeout value */
561 /* Task is not valid, remove it and try next one. */
562 silc_schedule_task_remove(queue, task);
564 if (queue->task == NULL)
569 /* Save the timeout */
571 schedule->timeout = &queue->timeout;
572 SILC_LOG_DEBUG(("timeout: sec=%d, usec=%d", schedule->timeout->tv_sec,
573 schedule->timeout->tv_usec));
577 /* Runs the scheduler once and then returns. */
579 bool silc_schedule_one(SilcSchedule schedule, int timeout_usecs)
581 struct timeval timeout;
584 SILC_LOG_DEBUG(("In scheduler loop"));
586 if (!schedule->is_locked)
587 SILC_SCHEDULE_LOCK(schedule);
589 /* Deliver signals if any has been set to be called */
590 if (schedule->signal_tasks) {
591 SILC_SCHEDULE_UNLOCK(schedule);
592 silc_schedule_internal_signals_call(schedule->internal, schedule);
593 schedule->signal_tasks = FALSE;
594 SILC_SCHEDULE_LOCK(schedule);
597 /* If the task queues aren't initialized or we aren't valid anymore
599 if ((!schedule->fd_queue && !schedule->timeout_queue
600 && !schedule->generic_queue) || schedule->valid == FALSE) {
601 SILC_LOG_DEBUG(("Scheduler not valid anymore, exiting"));
602 if (!schedule->is_locked)
603 SILC_SCHEDULE_UNLOCK(schedule);
607 /* Calculate next timeout for silc_select(). This is the timeout value
608 when at earliest some of the timeout tasks expire. */
609 silc_mutex_lock(schedule->timeout_queue->lock);
610 silc_schedule_select_timeout(schedule);
611 silc_mutex_unlock(schedule->timeout_queue->lock);
613 if (timeout_usecs >= 0) {
615 timeout.tv_usec = timeout_usecs;
616 schedule->timeout = &timeout;
619 SILC_SCHEDULE_UNLOCK(schedule);
621 /* This is the main select(). The program blocks here until some
622 of the selected file descriptors change status or the selected
624 SILC_LOG_DEBUG(("Select"));
625 ret = silc_select(schedule->fd_list, schedule->last_fd + 1,
628 SILC_SCHEDULE_LOCK(schedule);
635 SILC_LOG_ERROR(("Error in select(): %s", strerror(errno)));
639 silc_mutex_lock(schedule->timeout_queue->lock);
640 silc_schedule_dispatch_timeout(schedule, FALSE);
641 silc_mutex_unlock(schedule->timeout_queue->lock);
644 /* There is some data available now */
645 SILC_LOG_DEBUG(("Running non-timeout tasks"));
646 silc_schedule_dispatch_nontimeout(schedule);
650 if (!schedule->is_locked)
651 SILC_SCHEDULE_UNLOCK(schedule);
656 /* The SILC scheduler. This is actually the main routine in SILC programs.
657 When this returns the program is to be ended. Before this function can
658 be called, one must call silc_schedule_init function. */
660 void silc_schedule(SilcSchedule schedule)
662 SILC_LOG_DEBUG(("Running scheduler"));
664 if (schedule->valid == FALSE) {
665 SILC_LOG_ERROR(("Scheduler is not valid, stopping"));
669 SILC_SCHEDULE_LOCK(schedule);
670 schedule->is_locked = TRUE;
672 /* Start the scheduler loop */
673 while (silc_schedule_one(schedule, -1))
676 SILC_SCHEDULE_UNLOCK(schedule);
679 /* Wakes up the scheduler. This is used only in multi-threaded
680 environments where threads may add new tasks or remove old tasks
681 from task queues. This is called to wake up the scheduler in the
682 main thread so that it detects the changes in the task queues.
683 If threads support is not compiled in this function has no effect.
684 Implementation of this function is platform specific. */
686 void silc_schedule_wakeup(SilcSchedule schedule)
689 SILC_LOG_DEBUG(("Wakeup scheduler"));
690 SILC_SCHEDULE_LOCK(schedule);
691 silc_schedule_internal_wakeup(schedule->internal);
692 SILC_SCHEDULE_UNLOCK(schedule);
696 /* Returns the application specific context that was saved into the
697 scheduler in silc_schedule_init function. The context is also
698 returned to application in task callback functions, but this function
699 may be used to get it as well if needed. */
701 void *silc_schedule_get_context(SilcSchedule schedule)
703 return schedule->app_context;
706 /* Add new task to the scheduler */
708 SilcTask silc_schedule_task_add(SilcSchedule schedule, SilcUInt32 fd,
709 SilcTaskCallback callback, void *context,
710 long seconds, long useconds,
712 SilcTaskPriority priority)
718 if (!schedule->valid)
721 SILC_LOG_DEBUG(("Registering new task, fd=%d type=%d priority=%d", fd,
724 queue = SILC_SCHEDULE_GET_QUEUE(type);
726 /* If the task is generic task, we check whether this task has already
727 been registered. Generic tasks are registered only once and after that
728 the same task applies to all file descriptors to be registered. */
729 if (type == SILC_TASK_GENERIC) {
730 silc_mutex_lock(queue->lock);
733 SilcTask task = queue->task;
735 if ((task->callback == callback) && (task->context == context)) {
736 SILC_LOG_DEBUG(("Found matching generic task, using the match"));
738 silc_mutex_unlock(queue->lock);
740 /* Add the fd to be listened, the task found now applies to this
742 silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ, FALSE);
746 if (queue->task == task->next)
753 silc_mutex_unlock(queue->lock);
756 newtask = silc_calloc(1, sizeof(*newtask));
758 newtask->context = context;
759 newtask->callback = callback;
760 newtask->valid = TRUE;
761 newtask->priority = priority;
762 newtask->type = type;
763 newtask->next = newtask;
764 newtask->prev = newtask;
766 /* Create timeout if marked to be timeout task */
767 if (((seconds + useconds) > 0) && (type == SILC_TASK_TIMEOUT)) {
768 silc_gettimeofday(&newtask->timeout);
769 newtask->timeout.tv_sec += seconds + (useconds / 1000000L);
770 newtask->timeout.tv_usec += (useconds % 1000000L);
771 if (newtask->timeout.tv_usec > 999999L) {
772 newtask->timeout.tv_sec += 1;
773 newtask->timeout.tv_usec -= 1000000L;
778 /* If the task is non-timeout task we have to tell the scheduler that we
779 would like to have these tasks scheduled at some odd distant future. */
780 if (type != SILC_TASK_TIMEOUT)
781 silc_schedule_set_listen_fd(schedule, fd, SILC_TASK_READ, FALSE);
783 silc_mutex_lock(queue->lock);
785 /* Is this first task of the queue? */
786 if (queue->task == NULL) {
787 queue->task = newtask;
788 silc_mutex_unlock(queue->lock);
793 newtask = silc_task_add_timeout(queue, newtask, priority);
795 newtask = silc_task_add(queue, newtask, priority);
797 silc_mutex_unlock(queue->lock);
802 /* Removes a task from the scheduler */
804 void silc_schedule_task_del(SilcSchedule schedule, SilcTask task)
806 SilcTaskQueue queue = SILC_SCHEDULE_GET_QUEUE(task->type);
808 /* Unregister all tasks */
809 if (task == SILC_ALL_TASKS) {
811 SILC_LOG_DEBUG(("Unregistering all tasks at once"));
813 silc_mutex_lock(queue->lock);
816 silc_mutex_unlock(queue->lock);
825 if (queue->task == next->next)
830 silc_mutex_unlock(queue->lock);
834 SILC_LOG_DEBUG(("Unregistering task"));
836 silc_mutex_lock(queue->lock);
838 /* Unregister the specific task */
842 silc_mutex_unlock(queue->lock);
845 /* Remove task by fd */
847 void silc_schedule_task_del_by_fd(SilcSchedule schedule, SilcUInt32 fd)
849 SILC_LOG_DEBUG(("Unregister task by fd %d", fd));
851 silc_task_del_by_fd(schedule->timeout_queue, fd);
852 silc_task_del_by_fd(schedule->fd_queue, fd);
855 /* Remove task by task callback. */
857 void silc_schedule_task_del_by_callback(SilcSchedule schedule,
858 SilcTaskCallback callback)
860 SILC_LOG_DEBUG(("Unregister task by callback"));
862 silc_task_del_by_callback(schedule->timeout_queue, callback);
863 silc_task_del_by_callback(schedule->fd_queue, callback);
864 silc_task_del_by_callback(schedule->generic_queue, callback);
867 /* Remove task by context. */
869 void silc_schedule_task_del_by_context(SilcSchedule schedule, void *context)
871 SILC_LOG_DEBUG(("Unregister task by context"));
873 silc_task_del_by_context(schedule->timeout_queue, context);
874 silc_task_del_by_context(schedule->fd_queue, context);
875 silc_task_del_by_context(schedule->generic_queue, context);
878 /* Sets a file descriptor to be listened by select() in scheduler. One can
879 call this directly if wanted. This can be called multiple times for
880 one file descriptor to set different iomasks. */
882 void silc_schedule_set_listen_fd(SilcSchedule schedule, SilcUInt32 fd,
883 SilcTaskEvent mask, bool send_events)
888 if (!schedule->valid)
891 SILC_SCHEDULE_LOCK(schedule);
893 for (i = 0; i < schedule->max_fd; i++)
894 if (schedule->fd_list[i].fd == fd) {
895 schedule->fd_list[i].fd = fd;
896 schedule->fd_list[i].events = mask;
897 if (i > schedule->last_fd)
898 schedule->last_fd = i;
901 schedule->fd_list[i].revents = mask;
902 silc_schedule_dispatch_nontimeout(schedule);
908 for (i = 0; i < schedule->max_fd; i++)
909 if (schedule->fd_list[i].events == 0) {
910 schedule->fd_list[i].fd = fd;
911 schedule->fd_list[i].events = mask;
912 if (i > schedule->last_fd)
913 schedule->last_fd = i;
915 schedule->fd_list[i].revents = mask;
916 silc_schedule_dispatch_nontimeout(schedule);
921 SILC_SCHEDULE_UNLOCK(schedule);
924 /* Removes a file descriptor from listen list. */
926 void silc_schedule_unset_listen_fd(SilcSchedule schedule, SilcUInt32 fd)
930 SILC_SCHEDULE_LOCK(schedule);
932 SILC_LOG_DEBUG(("Unset listen fd %d", fd));
934 for (i = 0; i < schedule->max_fd; i++)
935 if (schedule->fd_list[i].fd == fd) {
936 schedule->fd_list[i].fd = 0;
937 schedule->fd_list[i].events = 0;
938 if (schedule->last_fd == i)
939 schedule->last_fd = schedule->max_fd - 1;
943 SILC_SCHEDULE_UNLOCK(schedule);
946 /* Register a new signal */
948 void silc_schedule_signal_register(SilcSchedule schedule, SilcUInt32 signal,
949 SilcTaskCallback callback, void *context)
951 silc_schedule_internal_signal_register(schedule->internal, signal,
955 /* Unregister a new signal */
957 void silc_schedule_signal_unregister(SilcSchedule schedule, SilcUInt32 signal,
958 SilcTaskCallback callback, void *context)
960 silc_schedule_internal_signal_unregister(schedule->internal, signal,
964 /* Call signal indicated by `signal'. */
966 void silc_schedule_signal_call(SilcSchedule schedule, SilcUInt32 signal)
968 /* Mark that signals needs to be delivered later. */
969 silc_schedule_internal_signal_call(schedule->internal, signal);
970 schedule->signal_tasks = TRUE;
973 /* Allocates a newtask task queue into the scheduler */
975 static void silc_task_queue_alloc(SilcTaskQueue *queue)
977 *queue = silc_calloc(1, sizeof(**queue));
978 silc_mutex_alloc(&(*queue)->lock);
981 /* Free's a task queue. */
983 static void silc_task_queue_free(SilcTaskQueue queue)
985 silc_mutex_free(queue->lock);
986 memset(queue, 'F', sizeof(*queue));
990 /* Return task by its fd. */
992 static SilcTask silc_task_find(SilcTaskQueue queue, SilcUInt32 fd)
1004 if (queue->task == next->next)
1012 /* Adds a non-timeout task into the task queue. This function is used
1013 by silc_task_register function. Returns a pointer to the registered
1016 static SilcTask silc_task_add(SilcTaskQueue queue, SilcTask newtask,
1017 SilcTaskPriority priority)
1019 SilcTask task, next, prev;
1021 /* Take the first task in the queue */
1025 case SILC_TASK_PRI_LOW:
1026 /* Lowest priority. The task is added at the end of the list. */
1028 newtask->prev = prev;
1029 newtask->next = task;
1030 prev->next = newtask;
1031 task->prev = newtask;
1033 case SILC_TASK_PRI_NORMAL:
1034 /* Normal priority. The task is added before lower priority tasks
1035 but after tasks with higher priority. */
1037 while(prev != task) {
1038 if (prev->priority > SILC_TASK_PRI_LOW)
1043 /* There are only lower priorities in the list, we will
1044 sit before them and become the first task in the queue. */
1046 newtask->prev = prev;
1047 newtask->next = task;
1048 task->prev = newtask;
1049 prev->next = newtask;
1051 /* We are now the first task in queue */
1052 queue->task = newtask;
1054 /* Found a spot from the list, add the task to the list. */
1056 newtask->prev = prev;
1057 newtask->next = next;
1058 prev->next = newtask;
1059 next->prev = newtask;
1070 /* Return the timeout task with smallest timeout. */
1072 static SilcTask silc_task_get_first(SilcTaskQueue queue, SilcTask first)
1074 SilcTask prev, task;
1086 if (silc_compare_timeval(&prev->timeout, &task->timeout))
1095 /* Adds a timeout task into the task queue. This function is used by
1096 silc_task_register function. Returns a pointer to the registered
1097 task. Timeout tasks are sorted by their timeout value in ascending
1098 order. The priority matters if there are more than one task with
1101 static SilcTask silc_task_add_timeout(SilcTaskQueue queue, SilcTask newtask,
1102 SilcTaskPriority priority)
1104 SilcTask task, prev, next;
1106 /* Take the first task in the queue */
1109 /* Take last task from the list */
1113 case SILC_TASK_PRI_LOW:
1114 /* Lowest priority. The task is added at the end of the list. */
1115 while(prev != task) {
1117 /* If we have longer timeout than with the task head of us
1118 we have found our spot. */
1119 if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
1122 /* If we are equal size of timeout we will be after it. */
1123 if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
1126 /* We have shorter timeout, compare to next one. */
1129 /* Found a spot from the list, add the task to the list. */
1131 newtask->prev = prev;
1132 newtask->next = next;
1133 prev->next = newtask;
1134 next->prev = newtask;
1137 /* Check if we are going to be the first task in the queue */
1138 if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
1140 if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
1143 /* We are now the first task in queue */
1144 queue->task = newtask;
1147 case SILC_TASK_PRI_NORMAL:
1148 /* Normal priority. The task is added before lower priority tasks
1149 but after tasks with higher priority. */
1150 while(prev != task) {
1152 /* If we have longer timeout than with the task head of us
1153 we have found our spot. */
1154 if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
1157 /* If we are equal size of timeout, priority kicks in place. */
1158 if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
1159 if (prev->priority >= SILC_TASK_PRI_NORMAL)
1162 /* We have shorter timeout or higher priority, compare to next one. */
1165 /* Found a spot from the list, add the task to the list. */
1167 newtask->prev = prev;
1168 newtask->next = next;
1169 prev->next = newtask;
1170 next->prev = newtask;
1173 /* Check if we are going to be the first task in the queue */
1174 if (silc_compare_timeval(&prev->timeout, &newtask->timeout))
1176 if (!silc_compare_timeval(&newtask->timeout, &prev->timeout))
1177 if (prev->priority >= SILC_TASK_PRI_NORMAL)
1180 /* We are now the first task in queue */
1181 queue->task = newtask;
1192 /* Removes (unregisters) a task from particular task queue. This function
1193 is used internally by scheduler. This must be called holding the
1196 static int silc_schedule_task_remove(SilcTaskQueue queue, SilcTask task)
1198 SilcTask first, old, next;
1200 if (!queue || !task)
1207 first = queue->task;
1209 /* Unregister all tasks in queue */
1210 if (task == SILC_ALL_TASKS) {
1211 SILC_LOG_DEBUG(("Removing all tasks at once"));
1226 SILC_LOG_DEBUG(("Removing task"));
1228 /* Unregister the task */
1232 SilcTask prev, next;
1239 if (prev == old && next == old)
1241 if (queue->task == old)
1242 queue->task = silc_task_get_first(queue, next);
1255 static void silc_task_del_by_fd(SilcTaskQueue queue, SilcUInt32 fd)
1259 silc_mutex_lock(queue->lock);
1262 silc_mutex_unlock(queue->lock);
1270 next->valid = FALSE;
1271 if (queue->task == next->next)
1276 silc_mutex_unlock(queue->lock);
1279 static void silc_task_del_by_callback(SilcTaskQueue queue,
1280 SilcTaskCallback callback)
1284 silc_mutex_lock(queue->lock);
1287 silc_mutex_unlock(queue->lock);
1294 if (next->callback == callback)
1295 next->valid = FALSE;
1296 if (queue->task == next->next)
1301 silc_mutex_unlock(queue->lock);
1304 static void silc_task_del_by_context(SilcTaskQueue queue, void *context)
1308 silc_mutex_lock(queue->lock);
1311 silc_mutex_unlock(queue->lock);
1318 if (next->context == context)
1319 next->valid = FALSE;
1320 if (queue->task == next->next)
1325 silc_mutex_unlock(queue->lock);